/*-------------------------------------------------------------------------
 *
 * predicate.c
 *	  POSTGRES谓词锁定
 *	  支持完全可串行化的事务隔离
 *
 *
 * 所采取的方法是实现可串行化快照隔离（SSI），如本论文中最初描述的：
 *
 *	Michael J. Cahill, Uwe Röhm, 和 Alan D. Fekete. 2008.
 *	快照数据库的可串行化隔离。
 *	在 SIGMOD '08：2008 ACM SIGMOD
 *	国际数据管理会议论文集，
 *	第729-738页，纽约，纽约，美国。ACM。
 *	http://doi.acm.org/10.1145/1376616.1376690
 *
 * 并在Cahill的博士论文中进一步阐述：
 *
 *	Michael James Cahill. 2009.
 *	快照数据库的可串行化隔离。
 *	悉尼数字论文。
 *	悉尼大学，信息技术学院。
 *	http://hdl.handle.net/2123/5353
 *
 *
 * 可串行化快照隔离（SSI）的谓词锁是SIREAD锁，它们与正常锁有很大的不同，因此需要一组独特的结构来处理它们。它们被用于检测在读取发生在写入之前时的rw冲突。（当写入发生在前时，读取事务可以通过检查MVCC数据来检测冲突。）
 *
 * (1) 除了实际读取的元组外，它们还必须覆盖基于谓词可能读取的元组的范围。这将要求通过对数据库对象（如页面、索引范围或整个表）进行的锁来建模谓词。
 *
 * (2) 它们必须保存在RAM中以便快速访问。因此，不可能始终保持元组级别的粒度——当分配用于存储这些的空间接近耗尽时，请求锁可能需要扫描某个单个事务持有多个可以合并为单个粗颗粒锁的细粒度锁的情况。
 *
 * (3) 它们永远不会阻塞任何东西；在这方面，它们更像是标志而不是锁；尽管它们引用数据库对象并用于标识与正常写锁的rw冲突。
 *
 * (4) 虽然它们与事务相关联，但必须在该事务成功提交后保持，直到所有重叠事务完成。这甚至意味着它们必须在事务的进程终止后仍然存在。然而，如果顶层事务被回滚，则会立即标记为可忽略，并且可以在之后的任何时间释放其SIREAD锁。
 *
 * (5) 仅仅创建SIREAD锁或检查与之冲突的事务是可串行化的事务。
 *
 * (6) 当发现顶层事务的写锁覆盖同一事务的现有SIREAD锁时，可以删除该SIREAD锁。
 *
 * (7) 可串行化事务的写入必须确保存在用于该事务的xact记录，其生命周期与事务相同（直到所有并发事务完成或事务被回滚），以便检测到与该事务的rw依赖性。
 *
 * 我们对只读事务使用了一种优化。在某些情况下，可以 نشان出只读事务的快照与其他事务绝不会发生冲突。这被称为"安全"快照（而已知不会发生冲突的快照被称为"不安全"）。然而，在所有并发读/写事务完成之前，无法确定快照是否安全。
 *
 * 一旦只读事务已知具有安全快照，它可以释放其谓词锁，并免除此后进一步的谓词锁跟踪。只读可延迟事务仅在安全快照上运行，必要时等待可用的快照。
 *
 *
 * 用于管理对谓词锁定共享内存对象的访问的轻量级锁必须按此顺序获取，并应以相反顺序释放：
 *
 *	 SerializableFinishedListLock
 *		- 保护已完成但可能仍然与仍然活动的事务重叠的事务列表。
 *
 *	 SerializablePredicateListLock
 *		- 保护事务持有的锁的链表。注意，这些锁本身也受到其各自锁目标的分区锁的保护；此锁仅影响连接到事务的锁的链表。
 *		- 所有事务共享这一单一锁（没有分区）。
 *		- 除了共享事务领导者的并行查询工作者外，除了运行活动事务的进程外，从不需要其他进程遍历该事务持有的锁列表。在并行情况下，会额外获取每个事务锁；见下文。
 *		- 另一进程修改事务列表的情况相对不频繁，但在出现谓词锁的索引页拆分和使用清空过程释放已加锁的页面等事件时确实发生。在此情况下删除锁时，锁本身包含必要的指针来将其从列表中删除。在这种情况下添加锁时，可以使用事务结构中的锚来添加锁。两者都不需要遍历列表。
 *		- 有时终止事务的列表清理不是按零售方式进行的，在这种情况下不需要锁。
 *		- 由于以上原因，访问其活动事务列表的进程始终使用共享锁，无论是遍历还是维护列表。这改善了常见访问模式的并发性。
 *		- 需要修改非自身活动事务列表的进程必须获取独占锁。
 *
 *	 SERIALIZABLEXACT的成员'perXactPredicateListLock'
 *		- 保护事务持有的谓词锁的链表。仅在并行模式下需要，其中多个后端共享同一SERIALIZABLEXACT对象。如果持有SerializablePredicateListLock独占，则不需要。
 *
 *	 PredicateLockHashPartitionLock(hashcode)
 *		- 相同的锁保护一个目标、该目标上的所有锁和目标上的锁的链表。
 *		- 当需要多个时，按地址升序获取。
 *		- 当需要所有锁（很少发生）时，使用PredicateLockHashPartitionLockByIndex(index)按索引升序获取。
 *
 *	 SerializableXactHashLock
 *		- 同时保护PredXact和SerializableXidHash。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/lmgr/predicate.c
 *
 *-------------------------------------------------------------------------
 */

/****
 * 接口例程
 *
 * 用于设置共享内存谓词锁结构的家务管理
 *		InitPredicateLocks(void)
 *		PredicateLockShmemSize(void)
 *
 * 谓词锁报告
 *		GetPredicateLockStatusData(void)
 *		PageIsPredicateLocked(Relation relation, BlockNumber blkno)
 *
 * 谓词锁维护
 *		GetSerializableTransactionSnapshot(Snapshot snapshot)
 *		SetSerializableTransactionSnapshot(Snapshot snapshot,
 *										   VirtualTransactionId *sourcevxid)
 *		RegisterPredicateLockingXid(void)
 *		PredicateLockRelation(Relation relation, Snapshot snapshot)
 *		PredicateLockPage(Relation relation, BlockNumber blkno,
 *						Snapshot snapshot)
 *		PredicateLockTID(Relation relation, ItemPointer tid, Snapshot snapshot,
 *						 TransactionId insert_xid)
 *		PredicateLockPageSplit(Relation relation, BlockNumber oldblkno,
 *							   BlockNumber newblkno)
 *		PredicateLockPageCombine(Relation relation, BlockNumber oldblkno,
 *								 BlockNumber newblkno)
 *		TransferPredicateLocksToHeapRelation(Relation relation)
 *		ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe)
 *
 * 冲突检测（可能还会触发回滚）
 *		CheckForSerializableConflictOut(Relation relation, TransactionId xid,
 *										Snapshot snapshot)
 *		CheckForSerializableConflictIn(Relation relation, ItemPointer tid,
 *									   BlockNumber blkno)
 *		CheckTableForSerializableConflictIn(Relation relation)
 *
 * 最终回滚检查
 *		PreCommit_CheckForSerializationFailure(void)
 *
 * 二阶段提交支持
 *		AtPrepare_PredicateLocks(void);
 *		PostPrepare_PredicateLocks(TransactionId xid);
 *		PredicateLockTwoPhaseFinish(TransactionId xid, bool isCommit);
 *		predicatelock_twophase_recover(TransactionId xid, uint16 info,
 *									   void *recdata, uint32 len);
 */

#include "postgres.h"

#include "access/parallel.h"
#include "access/slru.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "access/twophase.h"
#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "storage/predicate.h"
#include "storage/predicate_internals.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"

/* 注释下行以测试优雅降级代码。 */
/* #define TEST_SUMMARIZE_SERIAL */

/*
 * 首先测试选择性最高的字段，以提高性能。
 *
 * 如果满足以下所有条件，则 a 被 b 覆盖：
 *	1) a.database = b.database
 *	2) a.relation = b.relation
 *	3) b.offset 无效（b 是页面粒度或更高）
 *	4) 以下任一条成立：
 *		4a) a.offset 有效（a 是元组粒度）并且 a.page = b.page
 *	 或 4b) a.offset 无效且 b.page 无效（a 是
 *			页面粒度且 b 是关系粒度）
 */
#define TargetTagIsCoveredBy(covered_target, covering_target)			\
	((GET_PREDICATELOCKTARGETTAG_RELATION(covered_target) == /* (2) */	\
	  GET_PREDICATELOCKTARGETTAG_RELATION(covering_target))				\
	 && (GET_PREDICATELOCKTARGETTAG_OFFSET(covering_target) ==			\
		 InvalidOffsetNumber)								 /* (3) */	\
	 && (((GET_PREDICATELOCKTARGETTAG_OFFSET(covered_target) !=			\
		   InvalidOffsetNumber)								 /* (4a) */ \
		  && (GET_PREDICATELOCKTARGETTAG_PAGE(covering_target) ==		\
			  GET_PREDICATELOCKTARGETTAG_PAGE(covered_target)))			\
		 || ((GET_PREDICATELOCKTARGETTAG_PAGE(covering_target) ==		\
			  InvalidBlockNumber)							 /* (4b) */ \
			 && (GET_PREDICATELOCKTARGETTAG_PAGE(covered_target)		\
				 != InvalidBlockNumber)))								\
	 && (GET_PREDICATELOCKTARGETTAG_DB(covered_target) ==	 /* (1) */	\
		 GET_PREDICATELOCKTARGETTAG_DB(covering_target)))

/*
 * 谓词锁定目标和锁共享哈希表被分区以减少争用。
 * 要确定给定目标属于哪个分区，请使用 PredicateLockTargetTagHashCode() 计算标签的哈希码，然后应用以下宏之一。
 * 注意：NUM_PREDICATELOCK_PARTITIONS 必须是 2 的幂！
 */
#define PredicateLockHashPartition(hashcode) \
	((hashcode) % NUM_PREDICATELOCK_PARTITIONS)
#define PredicateLockHashPartitionLock(hashcode) \
	(&MainLWLockArray[PREDICATELOCK_MANAGER_LWLOCK_OFFSET + \
		PredicateLockHashPartition(hashcode)].lock)
#define PredicateLockHashPartitionLockByIndex(i) \
	(&MainLWLockArray[PREDICATELOCK_MANAGER_LWLOCK_OFFSET + (i)].lock)

#define NPREDICATELOCKTARGETENTS() \
	mul_size(max_predicate_locks_per_xact, add_size(MaxBackends, max_prepared_xacts))

#define SxactIsOnFinishedList(sxact) (!SHMQueueIsDetached(&((sxact)->finishedLink)))

/*
 * 请注意，一旦 sxact 通过了 PreCommit_CheckForSerializationFailure，
 * 就会标记为“已准备好”，即使它不使用 2PC。在此时，它不能再被中止。
 *
 * 提交后，PREPARED 标志仍然保持设置，因此 SxactIsCommitted
 * 意味着 SxactIsPrepared。
 */
#define SxactIsCommitted(sxact) (((sxact)->flags & SXACT_FLAG_COMMITTED) != 0)
#define SxactIsPrepared(sxact) (((sxact)->flags & SXACT_FLAG_PREPARED) != 0)
#define SxactIsRolledBack(sxact) (((sxact)->flags & SXACT_FLAG_ROLLED_BACK) != 0)
#define SxactIsDoomed(sxact) (((sxact)->flags & SXACT_FLAG_DOOMED) != 0)
#define SxactIsReadOnly(sxact) (((sxact)->flags & SXACT_FLAG_READ_ONLY) != 0)
#define SxactHasSummaryConflictIn(sxact) (((sxact)->flags & SXACT_FLAG_SUMMARY_CONFLICT_IN) != 0)
#define SxactHasSummaryConflictOut(sxact) (((sxact)->flags & SXACT_FLAG_SUMMARY_CONFLICT_OUT) != 0)
/*
 * 以下宏实际上意味着指定的事务与*提前提交的事务*存在冲突。
 * 很难用合理长度的名称来表示这一点。
 */
#define SxactHasConflictOut(sxact) (((sxact)->flags & SXACT_FLAG_CONFLICT_OUT) != 0)
#define SxactIsDeferrableWaiting(sxact) (((sxact)->flags & SXACT_FLAG_DEFERRABLE_WAITING) != 0)
#define SxactIsROSafe(sxact) (((sxact)->flags & SXACT_FLAG_RO_SAFE) != 0)
#define SxactIsROUnsafe(sxact) (((sxact)->flags & SXACT_FLAG_RO_UNSAFE) != 0)
#define SxactIsPartiallyReleased(sxact) (((sxact)->flags & SXACT_FLAG_PARTIALLY_RELEASED) != 0)

/*
 * 计算与 PREDICATELOCKTARGETTAG 关联的哈希码。
 *
 * 为了避免不必要的哈希码重新计算，我们尝试每个函数只执行一次，
 * 然后按需传递它。除了将哈希码传递给 hash_search_with_hash_value()，
 * 我们还可以从哈希码中提取锁分区号。
 */
#define PredicateLockTargetTagHashCode(predicatelocktargettag) \
	get_hash_value(PredicateLockTargetHash, predicatelocktargettag)

/*
 * 给定一个谓词锁定标签和目标的哈希，
 * 计算锁哈希。
 *
 * 为了使哈希码也依赖于事务，我们将 sxid 结构的地址
 * 异或进哈希码，左移以使分区编号位不变。由于这只是一个哈希，
 * 我们不在乎是否失去了地址的高阶位；使用一个中间变量来抑制
 * 将指针转换为整数的警告。
 */
#define PredicateLockHashCodeFromTargetHashCode(predicatelocktag, targethash) \
	((targethash) ^ ((uint32) PointerGetDatum((predicatelocktag)->myXact)) \
	 << LOG2_NUM_PREDICATELOCK_PARTITIONS)


/*
 * 我们通过此 SLRU 缓冲区区域访问旧的 xids。
 */
static SlruCtlData SerialSlruCtlData;

#define SerialSlruCtl			(&SerialSlruCtlData)

#define SERIAL_PAGESIZE			BLCKSZ
#define SERIAL_ENTRYSIZE			sizeof(SerCommitSeqNo)
#define SERIAL_ENTRIESPERPAGE	(SERIAL_PAGESIZE / SERIAL_ENTRYSIZE)

/*
 * 根据跟踪所有事务所需的页面数量设置最大页面数。
 */
#define SERIAL_MAX_PAGE			(MaxTransactionId / SERIAL_ENTRIESPERPAGE)

#define SerialNextPage(page) (((page) >= SERIAL_MAX_PAGE) ? 0 : (page) + 1)

#define SerialValue(slotno, xid) (*((SerCommitSeqNo *) \
	(SerialSlruCtl->shared->page_buffer[slotno] + \
	((((uint32) (xid)) % SERIAL_ENTRIESPERPAGE) * SERIAL_ENTRYSIZE))))

#define SerialPage(xid)	(((uint32) (xid)) / SERIAL_ENTRIESPERPAGE)

typedef struct SerialControlData
{
	int			headPage;		/* 最新初始化的页面 */
	TransactionId headXid;		/* SLRU 中最新的有效 Xid */
	TransactionId tailXid;		/* 我们可能感兴趣的最旧 xmin */
}			SerialControlData;

typedef struct SerialControlData *SerialControl;

static SerialControl serialControl;

/*
 * 当“已完成”列表中的最旧提交事务被移动到 SLRU 时，
 * 它的谓词锁将被移动到这个“虚拟”事务中，
 * 合并重复目标。当找到重复项时，使用后来的 commitSeqNo。
 */
static SERIALIZABLEXACT *OldCommittedSxact;


/*
 * 这些配置变量用于设置谓词锁表的大小
 * 并控制谓词锁的提升到更粗粒度，以便在
 * 面对内存压力时优雅地降低性能（主要是由于假阳性序列化
 * 失败）。
 */
int			max_predicate_locks_per_xact;	/* 由guc.c设置 */
int			max_predicate_locks_per_relation;	/* 由guc.c设置 */
int			max_predicate_locks_per_page;	/* 由guc.c设置 */

/*
 * 这提供了一个对象列表，用于跟踪参与谓词锁定的事务。
 * 列表中的条目大小固定，并驻留在共享内存中。
 * 条目的内存地址在其生命周期内必须保持固定。
 * 列表将被保护以防止外部并发更新；此代码中未提供任何
 * 管理机制。列表中的条目数量和每个条目的允许大小
 * 在创建时固定。
 */
static PredXactList PredXact;

/*
 * 这提供了一个 RWConflict 数据元素的池，用于在事务之间的冲突列表中使用。
 */
static RWConflictPoolHeader RWConflictPool;

/*
 * 谓词锁定哈希表位于共享内存中。
 * 每个后端都保持对它们的指针。
 */
static HTAB *SerializableXidHash;
static HTAB *PredicateLockTargetHash;
static HTAB *PredicateLockHash;
static SHM_QUEUE *FinishedSerializableTransactions;

/*
 * PredicateLockTargetHash 中虚拟条目的标签。通过暂时删除
 * 这个条目，你可以确保有足够的临时空间
 * 可用于在哈希表中插入一个条目。这是一个无效的标签。
 */
static const PREDICATELOCKTARGETTAG ScratchTargetTag = {0, 0, 0, 0};
static uint32 ScratchTargetTagHash;
static LWLock *ScratchPartitionLock;

/*
 * 用于确定何时将多个细粒度锁合并为一个粗粒度锁的
 * 本地哈希表。
 */
static HTAB *LocalPredicateLockHash = NULL;

/*
 * 保持对当前运行的可序列化事务（如果有）的指针
 * 以便快速引用。同时记住我们是否写入了可能
 * 导致 rw 冲突的内容。
 */
static SERIALIZABLEXACT *MySerializableXact = InvalidSerializableXact;
static bool MyXactDidWrite = false;

/*
 * SXACT_FLAG_RO_UNSAFE 优化可能会导致我们提前释放
 * MySerializableXact。如果在并行查询中发生这种情况，
 * 领导者需要在事务结束时推迟
 * SERIALIZABLEXACT 的销毁，因为工作线程仍然
 * 对其有引用。在这种情况下，领导者将其存储在这里。
 */
static SERIALIZABLEXACT *SavedSerializableXact = InvalidSerializableXact;

/* 本地函数 */

static SERIALIZABLEXACT *fc_CreatePredXact(void);
static void fc_ReleasePredXact(SERIALIZABLEXACT *fc_sxact);
static SERIALIZABLEXACT *fc_FirstPredXact(void);
static SERIALIZABLEXACT *fc_NextPredXact(SERIALIZABLEXACT *fc_sxact);

static bool fc_RWConflictExists(const SERIALIZABLEXACT *fc_reader, const SERIALIZABLEXACT *fc_writer);
static void fc_SetRWConflict(SERIALIZABLEXACT *fc_reader, SERIALIZABLEXACT *fc_writer);
static void fc_SetPossibleUnsafeConflict(SERIALIZABLEXACT *fc_roXact, SERIALIZABLEXACT *fc_activeXact);
static void fc_ReleaseRWConflict(RWConflict fc_conflict);
static void fc_FlagSxactUnsafe(SERIALIZABLEXACT *fc_sxact);

static bool fc_SerialPagePrecedesLogically(int fc_page1, int fc_page2);
static void fc_SerialInit(void);
static void fc_SerialAdd(TransactionId fc_xid, SerCommitSeqNo fc_minConflictCommitSeqNo);
static SerCommitSeqNo fc_SerialGetMinConflictCommitSeqNo(TransactionId fc_xid);
static void fc_SerialSetActiveSerXmin(TransactionId fc_xid);

static uint32 fc_predicatelock_hash(const void *fc_key, Size fc_keysize);
static void fc_SummarizeOldestCommittedSxact(void);
static Snapshot fc_GetSafeSnapshot(Snapshot fc_snapshot);
static Snapshot fc_GetSerializableTransactionSnapshotInt(Snapshot fc_snapshot,
													  VirtualTransactionId *fc_sourcevxid,
													  int fc_sourcepid);
static bool fc_PredicateLockExists(const PREDICATELOCKTARGETTAG *fc_targettag);
static bool fc_GetParentPredicateLockTag(const PREDICATELOCKTARGETTAG *fc_tag,
									  PREDICATELOCKTARGETTAG *fc_parent);
static bool fc_CoarserLockCovers(const PREDICATELOCKTARGETTAG *fc_newtargettag);
static void fc_RemoveScratchTarget(bool fc_lockheld);
static void fc_RestoreScratchTarget(bool fc_lockheld);
static void fc_RemoveTargetIfNoLongerUsed(PREDICATELOCKTARGET *fc_target,
									   uint32 fc_targettaghash);
static void fc_DeleteChildTargetLocks(const PREDICATELOCKTARGETTAG *fc_newtargettag);
static int	fc_MaxPredicateChildLocks(const PREDICATELOCKTARGETTAG *fc_tag);
static bool fc_CheckAndPromotePredicateLockRequest(const PREDICATELOCKTARGETTAG *fc_reqtag);
static void fc_DecrementParentLocks(const PREDICATELOCKTARGETTAG *fc_targettag);
static void fc_CreatePredicateLock(const PREDICATELOCKTARGETTAG *fc_targettag,
								uint32 fc_targettaghash,
								SERIALIZABLEXACT *fc_sxact);
static void fc_DeleteLockTarget(PREDICATELOCKTARGET *fc_target, uint32 fc_targettaghash);
static bool fc_TransferPredicateLocksToNewTarget(PREDICATELOCKTARGETTAG fc_oldtargettag,
											  PREDICATELOCKTARGETTAG fc_newtargettag,
											  bool fc_removeOld);
static void fc_PredicateLockAcquire(const PREDICATELOCKTARGETTAG *fc_targettag);
static void fc_DropAllPredicateLocksFromTable(Relation fc_relation,
										   bool fc_transfer);
static void fc_SetNewSxactGlobalXmin(void);
static void fc_ClearOldPredicateLocks(void);
static void fc_ReleaseOneSerializableXact(SERIALIZABLEXACT *fc_sxact, bool fc_partial,
									   bool fc_summarize);
static bool fc_XidIsConcurrent(TransactionId fc_xid);
static void fc_CheckTargetForConflictsIn(PREDICATELOCKTARGETTAG *fc_targettag);
static void fc_FlagRWConflict(SERIALIZABLEXACT *fc_reader, SERIALIZABLEXACT *fc_writer);
static void fc_OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *fc_reader,
													SERIALIZABLEXACT *fc_writer);
static void fc_CreateLocalPredicateLockHash(void);
static void fc_ReleasePredicateLocksLocal(void);


/*------------------------------------------------------------------------*/

/*
 * 该关系是否参与谓词锁定？临时和系统
 * 关系被排除在外，物化视图也是如此。
 */
static inline bool fc_PredicateLockingNeededForRelation(Relation fc_relation)
{
	return !(fc_relation->rd_id < FirstUnpinnedObjectId ||
			 RelationUsesLocalBuffers(fc_relation) ||
			 fc_relation->rd_rel->relkind == RELKIND_MATVIEW);
}


/*
 * 当公共接口方法被调用进行读取时，这是测试我们是否应该快速返回的
 * 测试。
 *
 * 注意：此函数有副作用！如果自上次调用以来，该事务被标记为
 * RO-safe，我们将释放所有谓词锁并重置
 * MySerializableXact。这样后续调用就可以快速返回。
 *
 * 这被标记为“inline”，以消除在序列化不需要的常见情况下
 * 函数调用的开销。
 */
static inline bool fc_SerializationNeededForRead(Relation fc_relation, Snapshot fc_snapshot)
{
	/* 如果这不是可序列化事务，则无需执行任何操作 */
	if (MySerializableXact == InvalidSerializableXact)
		return false;

	/*
	 * 在使用特殊快照进行扫描时不要获取锁或发生冲突。
	 * 这排除了诸如 CLUSTER 和 REINDEX 之类的操作。它们使用整体
	 * 函数 TransferPredicateLocksToHeapRelation() 和
	 * CheckTableForSerializableConflictIn() 参与序列化，
	 * 但相关的扫描不需要序列化。
	 */
	if (!IsMVCCSnapshot(fc_snapshot))
		return false;

	/*
	 * 检查我们是否刚刚变成了 "RO-safe"。如果是，我们立即释放
	 * 所有锁，因为它们不再需要。这也重置了
	 * MySerializableXact，因此后续对该函数的调用可以快速退出。
	 *
	 * 如果所有并发的 R/W 事务在不与早期快照发生冲突的情况下
	 * 提交，则这个事务被标记为 RO_SAFE，从而确保该事务不可能
	 * 发生冲突。
	 */
	if (SxactIsROSafe(MySerializableXact))
	{
		ReleasePredicateLocks(false, true);
		return false;
	}

	/* 检查该关系是否未参与谓词锁定 */
	if (!fc_PredicateLockingNeededForRelation(fc_relation))
		return false;

	return true;				/* 没有借口跳过谓词锁定 */
}

/*
 * 类似于 SerializationNeededForRead()，但在写入时调用。
 * 逻辑是相同的，但没有快照，并且我们不能是 RO-safe。
 */
static inline bool fc_SerializationNeededForWrite(Relation fc_relation)
{
	/* 如果这不是可序列化事务，则无需执行任何操作 */
	if (MySerializableXact == InvalidSerializableXact)
		return false;

	/* 检查该关系是否未参与谓词锁定 */
	if (!fc_PredicateLockingNeededForRelation(fc_relation))
		return false;

	return true;				/* 没有借口跳过谓词锁定 */
}


/*------------------------------------------------------------------------*/

/*
 * 这些函数是为这种特定类型的结构实现的简单列表。 
 * 如果将来有一种通用的共享内存列表，我们可能应该切换到那个。
 */
static SERIALIZABLEXACT * fc_CreatePredXact(void)
{
	PredXactListElement fc_ptle;

	fc_ptle = (PredXactListElement)
		SHMQueueNext(&PredXact->availableList,
					 &PredXact->availableList,
					 offsetof(PredXactListElementData, link));
	if (!fc_ptle)
		return NULL;

	SHMQueueDelete(&fc_ptle->link);
	SHMQueueInsertBefore(&PredXact->activeList, &fc_ptle->link);
	return &fc_ptle->sxact;
}

static void fc_ReleasePredXact(SERIALIZABLEXACT *fc_sxact)
{
	PredXactListElement fc_ptle;

	Assert(ShmemAddrIsValid(fc_sxact));

	fc_ptle = (PredXactListElement)
		(((char *) fc_sxact)
		 - offsetof(PredXactListElementData, sxact)
		 + offsetof(PredXactListElementData, link));
	SHMQueueDelete(&fc_ptle->link);
	SHMQueueInsertBefore(&PredXact->availableList, &fc_ptle->link);
}

static SERIALIZABLEXACT * fc_FirstPredXact(void)
{
	PredXactListElement fc_ptle;

	fc_ptle = (PredXactListElement)
		SHMQueueNext(&PredXact->activeList,
					 &PredXact->activeList,
					 offsetof(PredXactListElementData, link));
	if (!fc_ptle)
		return NULL;

	return &fc_ptle->sxact;
}

static SERIALIZABLEXACT * fc_NextPredXact(SERIALIZABLEXACT *fc_sxact)
{
	PredXactListElement fc_ptle;

	Assert(ShmemAddrIsValid(fc_sxact));

	fc_ptle = (PredXactListElement)
		(((char *) fc_sxact)
		 - offsetof(PredXactListElementData, sxact)
		 + offsetof(PredXactListElementData, link));
	fc_ptle = (PredXactListElement)
		SHMQueueNext(&PredXact->activeList,
					 &fc_ptle->link,
					 offsetof(PredXactListElementData, link));
	if (!fc_ptle)
		return NULL;

	return &fc_ptle->sxact;
}

/*------------------------------------------------------------------------*/

/*
 * 这些函数管理对 RWConflict 池和列表的基本访问。
 */
static bool fc_RWConflictExists(const SERIALIZABLEXACT *fc_reader, const SERIALIZABLEXACT *fc_writer)
{
	RWConflict	fc_conflict;

	Assert(fc_reader != fc_writer);

	/* 首先检查声称的冲突的端点。 */
	if (SxactIsDoomed(fc_reader)
		|| SxactIsDoomed(fc_writer)
		|| SHMQueueEmpty(&fc_reader->outConflicts)
		|| SHMQueueEmpty(&fc_writer->inConflicts))
		return false;

	/* 可能存在冲突；遍历列表以找出。 */
	fc_conflict = (RWConflict)
		SHMQueueNext(&fc_reader->outConflicts,
					 &fc_reader->outConflicts,
					 offsetof(RWConflictData, outLink));
	while (fc_conflict)
	{
		if (fc_conflict->sxactIn == fc_writer)
			return true;
		fc_conflict = (RWConflict)
			SHMQueueNext(&fc_reader->outConflicts,
						 &fc_conflict->outLink,
						 offsetof(RWConflictData, outLink));
	}

	/* 未找到冲突。 */
	return false;
}

static void fc_SetRWConflict(SERIALIZABLEXACT *fc_reader, SERIALIZABLEXACT *fc_writer)
{
	RWConflict	fc_conflict;

	Assert(fc_reader != fc_writer);
	Assert(!fc_RWConflictExists(fc_reader, fc_writer));

	fc_conflict = (RWConflict)
		SHMQueueNext(&RWConflictPool->availableList,
					 &RWConflictPool->availableList,
					 offsetof(RWConflictData, outLink));
	if (!fc_conflict)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("not enough elements in RWConflictPool to record a read/write conflict"),
				 errhint("You might need to run fewer transactions at a time or increase max_connections.")));

	SHMQueueDelete(&fc_conflict->outLink);

	fc_conflict->sxactOut = fc_reader;
	fc_conflict->sxactIn = fc_writer;
	SHMQueueInsertBefore(&fc_reader->outConflicts, &fc_conflict->outLink);
	SHMQueueInsertBefore(&fc_writer->inConflicts, &fc_conflict->inLink);
}

static void fc_SetPossibleUnsafeConflict(SERIALIZABLEXACT *fc_roXact,
						  SERIALIZABLEXACT *fc_activeXact)
{
	RWConflict	fc_conflict;

	Assert(fc_roXact != fc_activeXact);
	Assert(SxactIsReadOnly(fc_roXact));
	Assert(!SxactIsReadOnly(fc_activeXact));

	fc_conflict = (RWConflict)
		SHMQueueNext(&RWConflictPool->availableList,
					 &RWConflictPool->availableList,
					 offsetof(RWConflictData, outLink));
	if (!fc_conflict)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("not enough elements in RWConflictPool to record a potential read/write conflict"),
				 errhint("You might need to run fewer transactions at a time or increase max_connections.")));

	SHMQueueDelete(&fc_conflict->outLink);

	fc_conflict->sxactOut = fc_activeXact;
	fc_conflict->sxactIn = fc_roXact;
	SHMQueueInsertBefore(&fc_activeXact->possibleUnsafeConflicts,
						 &fc_conflict->outLink);
	SHMQueueInsertBefore(&fc_roXact->possibleUnsafeConflicts,
						 &fc_conflict->inLink);
}

static void fc_ReleaseRWConflict(RWConflict fc_conflict)
{
	SHMQueueDelete(&fc_conflict->inLink);
	SHMQueueDelete(&fc_conflict->outLink);
	SHMQueueInsertBefore(&RWConflictPool->availableList, &fc_conflict->outLink);
}

static void fc_FlagSxactUnsafe(SERIALIZABLEXACT *fc_sxact)
{
	RWConflict	fc_conflict,
				fc_nextConflict;

	Assert(SxactIsReadOnly(fc_sxact));
	Assert(!SxactIsROSafe(fc_sxact));

	fc_sxact->flags |= SXACT_FLAG_RO_UNSAFE;

	/*
	 * 我们知道这不是一个安全的快照，因此可以停止寻找其他
	 * 潜在冲突。
	 */
	fc_conflict = (RWConflict)
		SHMQueueNext(&fc_sxact->possibleUnsafeConflicts,
					 &fc_sxact->possibleUnsafeConflicts,
					 offsetof(RWConflictData, inLink));
	while (fc_conflict)
	{
		fc_nextConflict = (RWConflict)
			SHMQueueNext(&fc_sxact->possibleUnsafeConflicts,
						 &fc_conflict->inLink,
						 offsetof(RWConflictData, inLink));

		Assert(!SxactIsReadOnly(fc_conflict->sxactOut));
		Assert(fc_sxact == fc_conflict->sxactIn);

		fc_ReleaseRWConflict(fc_conflict);

		fc_conflict = fc_nextConflict;
	}
}

/*------------------------------------------------------------------------*/

/*
 * 决定序列页号在截断目的下是否“更旧”。
 * 类似于 CLOGPagePrecedes()。
 */
static bool fc_SerialPagePrecedesLogically(int fc_page1, int fc_page2)
{
	TransactionId fc_xid1;
	TransactionId fc_xid2;

	fc_xid1 = ((TransactionId) fc_page1) * SERIAL_ENTRIESPERPAGE;
	fc_xid1 += FirstNormalTransactionId + 1;
	fc_xid2 = ((TransactionId) fc_page2) * SERIAL_ENTRIESPERPAGE;
	fc_xid2 += FirstNormalTransactionId + 1;

	return (TransactionIdPrecedes(fc_xid1, fc_xid2) &&
			TransactionIdPrecedes(fc_xid1, fc_xid2 + SERIAL_ENTRIESPERPAGE - 1));
}

#ifdef USE_ASSERT_CHECKING
static void fc_SerialPagePrecedesLogicallyUnitTests(void)
{
	int			fc_per_page = SERIAL_ENTRIESPERPAGE,
				fc_offset = fc_per_page / 2;
	int			fc_newestPage,
				fc_oldestPage,
				fc_headPage,
				fc_targetPage;
	TransactionId fc_newestXact,
				fc_oldestXact;

	/* GetNewTransactionId() 已分配了最后一个可以安全使用的 XID。 */
	fc_newestPage = 2 * SLRU_PAGES_PER_SEGMENT - 1;	/* 没有什么特别的 */
	fc_newestXact = fc_newestPage * fc_per_page + fc_offset;
	Assert(fc_newestXact / fc_per_page == fc_newestPage);
	fc_oldestXact = fc_newestXact + 1;
	fc_oldestXact -= 1U << 31;
	fc_oldestPage = fc_oldestXact / fc_per_page;

	/*
	 * 在这种情况下，SLRU headPage 与最后 ~1000 个分配的 XID 相关。
	 * oldestXact 完成，~2B XID 自开始以来已过去。
	 * 进一步的事务导致我们将 oldestXact 汇总到 
	 * tailPage。 函数必须返回 false，以便 SerialAdd() 不会将 tailPage 置为零 
	 * （该页可能包含其他旧的、最近完成的 XID 的条目）并半个 SLRU。 
	 * 达到这一点需要在单用户模式下消耗 ~2B XID，这是一个微不足道的可能性。
	 */
	fc_headPage = fc_newestPage;
	fc_targetPage = fc_oldestPage;
	Assert(!fc_SerialPagePrecedesLogically(fc_headPage, fc_targetPage));

	/*
	 * 在这种情况下，SLRU headPage 与 oldestXact 相关。 
	 * 我们正在汇总一个接近 newestXact 的 XID。 
     * （假设很少有其他 XID 使用 SERIALIZABLE，因此 headPage 的推进很小。 
     * 假设 oldestXact 运行时间很长，直到最近才到达 SLRU。） 
	 * 函数必须返回 true，以使 SerialAdd() 创建 targetPage。
	 *
	 * 今天的实现错误地处理了这个情况，但没有足够的重要性去修复。 
	 * 通过断言对此前页面的正确处理，验证缺陷仅影响一页。 
	 * 达到这个情况需要在单用户模式下消耗 ~2B XID，微不足道的
	 * 可能性。 此外，如果确实发生，后果会很轻微，即新的事务在 SimpleLruReadPage() 中失败。
	 */
	fc_headPage = fc_oldestPage;
	fc_targetPage = fc_newestPage;
	Assert(fc_SerialPagePrecedesLogically(fc_headPage, fc_targetPage - 1));
#if 0
	Assert(SerialPagePrecedesLogically(fc_headPage, fc_targetPage));
#endif
}
#endif

/*
 * 初始化以跟踪旧的可序列化已提交的 xids。
 */
static void fc_SerialInit(void)
{
	bool		fc_found;

	/*
	 * 设置 pg_serial 数据的 SLRU 管理。
	 */
	SerialSlruCtl->PagePrecedes = fc_SerialPagePrecedesLogically;
	SimpleLruInit(SerialSlruCtl, "Serial",
				  NUM_SERIAL_BUFFERS, 0, SerialSLRULock, "pg_serial",
				  LWTRANCHE_SERIAL_BUFFER, SYNC_HANDLER_NONE);
#ifdef USE_ASSERT_CHECKING
	fc_SerialPagePrecedesLogicallyUnitTests();
#endif
	SlruPagePrecedesUnitTests(SerialSlruCtl, SERIAL_ENTRIESPERPAGE);

	/*
	 * 创建或附加到 SerialControl 结构。
	 */
	serialControl = (SerialControl)
		ShmemInitStruct("SerialControlData", sizeof(SerialControlData), &fc_found);

	Assert(fc_found == IsUnderPostmaster);
	if (!fc_found)
	{
		/*
		 * 设置控制信息以反映空的 SLRU。
		 */
		serialControl->headPage = -1;
		serialControl->headXid = InvalidTransactionId;
		serialControl->tailXid = InvalidTransactionId;
	}
}

/*
 * 记录一个已提交的读写可序列化 xid 及其有 rw-conflict 的任何事务的最小
 * commitSeqNo。无效的 commitSeqNo 表示没有来自 xid 的冲突。
 */
static void fc_SerialAdd(TransactionId fc_xid, SerCommitSeqNo fc_minConflictCommitSeqNo)
{
	TransactionId fc_tailXid;
	int			fc_targetPage;
	int			fc_slotno;
	int			fc_firstZeroPage;
	bool		fc_isNewPage;

	Assert(TransactionIdIsValid(fc_xid));

	fc_targetPage = SerialPage(fc_xid);

	LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);

	/*
	 * 如果 'xid' 比全局 xmin (== tailXid) 旧，那么无须
	 * 存储它。 这发生在持有全局 xmin 的最旧事务刚刚完成时，
	 * 使 'xid' 不再重要，但 ClearOldPredicateLocks() 还没有运行。
	 */
	fc_tailXid = serialControl->tailXid;
	if (!TransactionIdIsValid(fc_tailXid) || TransactionIdPrecedes(fc_xid, fc_tailXid))
	{
		LWLockRelease(SerialSLRULock);
		return;
	}

	/*
	 * 如果 SLRU 当前未使用，则在开始使用之前将 
	 * tailXid 到 headXid 的整个活动区域清零。 否则，仅在我们推进 
	 * headXid 时将进入 tailXid-headXid 范围的新页面清零。
	 */
	if (serialControl->headPage < 0)
	{
		fc_firstZeroPage = SerialPage(fc_tailXid);
		fc_isNewPage = true;
	}
	else
	{
		fc_firstZeroPage = SerialNextPage(serialControl->headPage);
		fc_isNewPage = fc_SerialPagePrecedesLogically(serialControl->headPage,
												fc_targetPage);
	}

	if (!TransactionIdIsValid(serialControl->headXid)
		|| TransactionIdFollows(fc_xid, serialControl->headXid))
		serialControl->headXid = fc_xid;
	if (fc_isNewPage)
		serialControl->headPage = fc_targetPage;

	if (fc_isNewPage)
	{
		/* 初始化中介页面。 */
		while (fc_firstZeroPage != fc_targetPage)
		{
			(void) SimpleLruZeroPage(SerialSlruCtl, fc_firstZeroPage);
			fc_firstZeroPage = SerialNextPage(fc_firstZeroPage);
		}
		fc_slotno = SimpleLruZeroPage(SerialSlruCtl, fc_targetPage);
	}
	else
		fc_slotno = SimpleLruReadPage(SerialSlruCtl, fc_targetPage, true, fc_xid);

	SerialValue(fc_slotno, fc_xid) = fc_minConflictCommitSeqNo;
	SerialSlruCtl->shared->page_dirty[fc_slotno] = true;

	LWLockRelease(SerialSLRULock);
}

/*
 * 获取给定 xid 的任何冲突的最小 commitSeqNo。 
 * 对于存在但没有冲突的事务，将返回 InvalidSerCommitSeqNo。
 */
static SerCommitSeqNo
fc_SerialGetMinConflictCommitSeqNo(TransactionId fc_xid)
{
	TransactionId fc_headXid;
	TransactionId fc_tailXid;
	SerCommitSeqNo fc_val;
	int			fc_slotno;

	Assert(TransactionIdIsValid(fc_xid));

	LWLockAcquire(SerialSLRULock, LW_SHARED);
	fc_headXid = serialControl->headXid;
	fc_tailXid = serialControl->tailXid;
	LWLockRelease(SerialSLRULock);

	if (!TransactionIdIsValid(fc_headXid))
		return 0;

	Assert(TransactionIdIsValid(fc_tailXid));

	if (TransactionIdPrecedes(fc_xid, fc_tailXid)
		|| TransactionIdFollows(fc_xid, fc_headXid))
		return 0;

	/*
	 * 以下函数必须在不持有 SerialSLRULock 的情况下调用，
	 * 但返回时将持有该锁，必须释放该锁。
	 */
	fc_slotno = SimpleLruReadPage_ReadOnly(SerialSlruCtl,
										SerialPage(fc_xid), fc_xid);
	fc_val = SerialValue(fc_slotno, fc_xid);
	LWLockRelease(SerialSLRULock);
	return fc_val;
}

/*
 * 每当有一个新的 xmin 用于活动的可序列化事务时调用这个。
 * 我们不需要保留关于在此之前的事务的信息。 
 * InvalidTransactionId 表示没有活动，因此 SLRU 中的所有内容都可以丢弃。
 */
static void fc_SerialSetActiveSerXmin(TransactionId fc_xid)
{
	LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);

	/*
	 * 当没有 sxacts 活动时，没有重叠，将 xid 值设置为
	 * 无效，以表明没有有效条目。 但不要清除 headPage。
	 * 一个新的 xmin 仍然可能落在该页上，我们不希望
	 * 反复将同一页清零。
	 */
	if (!TransactionIdIsValid(fc_xid))
	{
		serialControl->tailXid = InvalidTransactionId;
		serialControl->headXid = InvalidTransactionId;
		LWLockRelease(SerialSLRULock);
		return;
	}

	/*
	 * 当我们恢复已准备的事务时，全局 xmin 可能会根据恢复顺序向后移动。
	 * 通常这是不可以的，但在恢复过程中不会有可序列化事务提交，
	 * 因此 SLRU 是空的，我们可以这样做。
	 */
	if (RecoveryInProgress())
	{
		Assert(serialControl->headPage < 0);
		if (!TransactionIdIsValid(serialControl->tailXid)
			|| TransactionIdPrecedes(fc_xid, serialControl->tailXid))
		{
			serialControl->tailXid = fc_xid;
		}
		LWLockRelease(SerialSLRULock);
		return;
	}

	Assert(!TransactionIdIsValid(serialControl->tailXid)
		   || TransactionIdFollows(fc_xid, serialControl->tailXid));

	serialControl->tailXid = fc_xid;

	LWLockRelease(SerialSLRULock);
}

/*
 * 执行检查点 --- 无论是关闭期间，还是按需
 *
 * 我们没有任何需要在重启时存活的数据，但这是一个
 * 方便的地方去截断 SLRU。
 */
void CheckPointPredicate(void)
{
	int			fc_tailPage;

	LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);

	/* 如果 SLRU 当前未使用，则快速退出。 */
	if (serialControl->headPage < 0)
	{
		LWLockRelease(SerialSLRULock);
		return;
	}

	if (TransactionIdIsValid(serialControl->tailXid))
	{
		/* 我们可以将 SLRU 截断到包含 tailXid 的页面 */
		fc_tailPage = SerialPage(serialControl->tailXid);
	}
	else
	{
		
/*---------
		 * SLRU不再需要。在我们将头部设置为无效之前，先截断到头部。
		 *
		 * XXX: 有可能SLRU在XID回绕发生之前不再需要，这样我们留下的包含headPage的段将再次显示为新。 在这种情况下，它不会被删除，直到XID视野向前推进足够使其再次变为当前。
		 *
		 * XXX: 这应该发生在vac_truncate_clog()中，而不是在检查点中。 考虑以下场景，从一个没有进行中事务的系统开始，VACUUM FREEZE最大化了oldestXact：
		 * - 开始一个可串行化事务。
		 * - 启动、完成并总结一个可串行化事务，创建一个SLRU页面。
		 * - 消耗XIDs以达到xidStopLimit。
		 * - 完成所有事务。由于长时间运行的可串行化事务，早期的检查点没有触及headPage。下一个检查点将更改其内容，但该检查点发生在场景结束之后。
		 * - 执行VACUUM以推进XID限制。
		 * - 消耗约200万个XIDs，跨越前一个xidWrapLimit。
		 * - 启动、完成并总结一个可串行化事务。 SerialAdd()拒绝创建targetPage，因为headPage相对于该targetPage并不被认为是过去的。 触发总结的事务在SimpleLruReadPage()中失败。
		 */
		fc_tailPage = serialControl->headPage;
		serialControl->headPage = -1;
	}

	LWLockRelease(SerialSLRULock);

	/* 截断不再需要的页面 */
	SimpleLruTruncate(SerialSlruCtl, fc_tailPage);

	/*
	 * 将脏SLRU页面写入磁盘
	 *
	 * 从正确性角度来看，这实际上并不是必要的。我们这样做只是作为调试帮助。
	 *
	 * 我们在截断后进行此操作，以避免在删除它们所在的文件之前写入页面，这完全是毫无意义的。
	 */
	SimpleLruWriteAll(SerialSlruCtl, true);
}

/*------------------------------------------------------------------------*/

/***********************
 * InitPredicateLocks -- 初始化谓词锁定数据结构。
 *
 * 这个函数从 CreateSharedMemoryAndSemaphores() 被调用，更多注释请参见该函数。
 * 在正常的邮政管理员情况下，这里创建共享哈希表。
 * 后端通过 fork() 继承指向共享表的指针。
 * 在 EXEC_BACKEND 的情况下，每个后端重新执行此代码以获取指向已经存在的共享哈希表的指针。
 ***********************/
void InitPredicateLocks(void)
{
	HASHCTL		fc_info;
	long		fc_max_table_size;
	Size		fc_requestSize;
	bool		fc_found;

#ifndef EXEC_BACKEND
	Assert(!IsUnderPostmaster);
#endif

	/*
	 * 计算谓词锁定目标哈希表的大小。注意这些计算必须与 PredicateLockShmemSize 一致！
	 */
	fc_max_table_size = NPREDICATELOCKTARGETENTS();

	/*
	 * 为 PREDICATELOCKTARGET 结构分配哈希表。这存储每个谓词锁定目标的信息。
	 */
	fc_info.keysize = sizeof(PREDICATELOCKTARGETTAG);
	fc_info.entrysize = sizeof(PREDICATELOCKTARGET);
	fc_info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;

	PredicateLockTargetHash = ShmemInitHash("PREDICATELOCKTARGET hash",
											fc_max_table_size,
											fc_max_table_size,
											&fc_info,
											HASH_ELEM | HASH_BLOBS |
											HASH_PARTITION | HASH_FIXED_SIZE);

	/*
	 * 在哈希表中保留一个虚拟条目；我们使用它来确保每当我们需要拆分或合并页面时始终有一个条目可用，
	 * 因为在那里用完空间可能意味着中止一个非可串行化事务。
	 */
	if (!IsUnderPostmaster)
	{
		(void) hash_search(PredicateLockTargetHash, &ScratchTargetTag,
						   HASH_ENTER, &fc_found);
		Assert(!fc_found);
	}

	/* 预先计算虚拟条目的哈希和分区锁 */
	ScratchTargetTagHash = PredicateLockTargetTagHashCode(&ScratchTargetTag);
	ScratchPartitionLock = PredicateLockHashPartitionLock(ScratchTargetTagHash);

	/*
	 * 为 PREDICATELOCK 结构分配哈希表。这存储每个目标的事务锁信息。
	 */
	fc_info.keysize = sizeof(PREDICATELOCKTAG);
	fc_info.entrysize = sizeof(PREDICATELOCK);
	fc_info.hash = fc_predicatelock_hash;
	fc_info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;

	/* 假设每个目标平均有 2 个事务 */
	fc_max_table_size *= 2;

	PredicateLockHash = ShmemInitHash("PREDICATELOCK hash",
									  fc_max_table_size,
									  fc_max_table_size,
									  &fc_info,
									  HASH_ELEM | HASH_FUNCTION |
									  HASH_PARTITION | HASH_FIXED_SIZE);

	/*
	 * 计算可串行化事务哈希表的大小。注意这些计算必须与 PredicateLockShmemSize 一致！
	 */
	fc_max_table_size = (MaxBackends + max_prepared_xacts);

	/*
	 * 分配一个列表以存储参与谓词锁定的事务信息。
	 *
	 * 假设每个后端平均有 10 个谓词锁定事务。
	 * 这允许在需要为存储在 SLRU 中的“虚拟”事务汇总数据之前积极清理细节。
	 */
	fc_max_table_size *= 10;

	PredXact = ShmemInitStruct("PredXactList",
							   PredXactListDataSize,
							   &fc_found);
	Assert(fc_found == IsUnderPostmaster);
	if (!fc_found)
	{
		int			fc_i;

		SHMQueueInit(&PredXact->availableList);
		SHMQueueInit(&PredXact->activeList);
		PredXact->SxactGlobalXmin = InvalidTransactionId;
		PredXact->SxactGlobalXminCount = 0;
		PredXact->WritableSxactCount = 0;
		PredXact->LastSxactCommitSeqNo = FirstNormalSerCommitSeqNo - 1;
		PredXact->CanPartialClearThrough = 0;
		PredXact->HavePartialClearedThrough = 0;
		fc_requestSize = mul_size((Size) fc_max_table_size,
							   PredXactListElementDataSize);
		PredXact->element = ShmemAlloc(fc_requestSize);
		/* 将所有元素添加到可用列表中，清理。 */
		memset(PredXact->element, 0, fc_requestSize);
		for (fc_i = 0; fc_i < fc_max_table_size; fc_i++)
		{
			LWLockInitialize(&PredXact->element[fc_i].sxact.perXactPredicateListLock,
							 LWTRANCHE_PER_XACT_PREDICATE_LIST);
			SHMQueueInsertBefore(&(PredXact->availableList),
								 &(PredXact->element[fc_i].link));
		}
		PredXact->OldCommittedSxact = fc_CreatePredXact();
		SetInvalidVirtualTransactionId(PredXact->OldCommittedSxact->vxid);
		PredXact->OldCommittedSxact->prepareSeqNo = 0;
		PredXact->OldCommittedSxact->commitSeqNo = 0;
		PredXact->OldCommittedSxact->SeqNo.lastCommitBeforeSnapshot = 0;
		SHMQueueInit(&PredXact->OldCommittedSxact->outConflicts);
		SHMQueueInit(&PredXact->OldCommittedSxact->inConflicts);
		SHMQueueInit(&PredXact->OldCommittedSxact->predicateLocks);
		SHMQueueInit(&PredXact->OldCommittedSxact->finishedLink);
		SHMQueueInit(&PredXact->OldCommittedSxact->possibleUnsafeConflicts);
		PredXact->OldCommittedSxact->topXid = InvalidTransactionId;
		PredXact->OldCommittedSxact->finishedBefore = InvalidTransactionId;
		PredXact->OldCommittedSxact->xmin = InvalidTransactionId;
		PredXact->OldCommittedSxact->flags = SXACT_FLAG_COMMITTED;
		PredXact->OldCommittedSxact->pid = 0;
		PredXact->OldCommittedSxact->pgprocno = INVALID_PGPROCNO;
	}
	/* 这从不改变，因此让我们保留一个本地副本。 */
	OldCommittedSxact = PredXact->OldCommittedSxact;

	/*
	 * 为 SERIALIZABLEXID 结构分配哈希表。这存储已访问数据的可串行化事务的每个 xid 信息。
	 */
	fc_info.keysize = sizeof(SERIALIZABLEXIDTAG);
	fc_info.entrysize = sizeof(SERIALIZABLEXID);

	SerializableXidHash = ShmemInitHash("SERIALIZABLEXID hash",
										fc_max_table_size,
										fc_max_table_size,
										&fc_info,
										HASH_ELEM | HASH_BLOBS |
										HASH_FIXED_SIZE);

	/*
	 * 分配空间以追踪附属于事务的 rw 冲突列表。
	 *
	 * 假设每个事务平均有 5 个冲突。计算表明，即使在最大连接数为 200 的最糟糕负载下，这也将防止资源耗尽，
	 * 200 个连接用可串行化事务猛击数据库。超出这一点，在尝试标记冲突时可能会偶尔取消事务。这可能是可以接受的。
	 */
	fc_max_table_size *= 5;

	RWConflictPool = ShmemInitStruct("RWConflictPool",
									 RWConflictPoolHeaderDataSize,
									 &fc_found);
	Assert(fc_found == IsUnderPostmaster);
	if (!fc_found)
	{
		int			fc_i;

		SHMQueueInit(&RWConflictPool->availableList);
		fc_requestSize = mul_size((Size) fc_max_table_size,
							   RWConflictDataSize);
		RWConflictPool->element = ShmemAlloc(fc_requestSize);
		/* 将所有元素添加到可用列表中，清理。 */
		memset(RWConflictPool->element, 0, fc_requestSize);
		for (fc_i = 0; fc_i < fc_max_table_size; fc_i++)
		{
			SHMQueueInsertBefore(&(RWConflictPool->availableList),
								 &(RWConflictPool->element[fc_i].outLink));
		}
	}

	/*
	 * 创建或附加到已完成可串行化事务列表的头。
	 */
	FinishedSerializableTransactions = (SHM_QUEUE *)
		ShmemInitStruct("FinishedSerializableTransactions",
						sizeof(SHM_QUEUE),
						&fc_found);
	Assert(fc_found == IsUnderPostmaster);
	if (!fc_found)
		SHMQueueInit(FinishedSerializableTransactions);

	/*
	 * 初始化旧提交的可串行化事务的 SLRU 存储。
	 */
	fc_SerialInit();
}

/*
 * 估计用于谓词锁表的共享内存空间
 */
Size PredicateLockShmemSize(void)
{
	Size		fc_size = 0;
	long		fc_max_table_size;

	/* 谓词锁定目标哈希表 */
	fc_max_table_size = NPREDICATELOCKTARGETENTS();
	fc_size = add_size(fc_size, hash_estimate_size(fc_max_table_size,
											 sizeof(PREDICATELOCKTARGET)));

	/* 谓词锁哈希表 */
	fc_max_table_size *= 2;
	fc_size = add_size(fc_size, hash_estimate_size(fc_max_table_size,
											 sizeof(PREDICATELOCK)));

	/*
	 * 由于 NPREDICATELOCKTARGETENTS 只是一个估计，添加 10% 的安全边际。
	 */
	fc_size = add_size(fc_size, fc_size / 10);

	/* 事务列表 */
	fc_max_table_size = MaxBackends + max_prepared_xacts;
	fc_max_table_size *= 10;
	fc_size = add_size(fc_size, PredXactListDataSize);
	fc_size = add_size(fc_size, mul_size((Size) fc_max_table_size,
								   PredXactListElementDataSize));

	/* 事务 xid 表 */
	fc_size = add_size(fc_size, hash_estimate_size(fc_max_table_size,
											 sizeof(SERIALIZABLEXID)));

	/* rw 冲突池 */
	fc_max_table_size *= 5;
	fc_size = add_size(fc_size, RWConflictPoolHeaderDataSize);
	fc_size = add_size(fc_size, mul_size((Size) fc_max_table_size,
								   RWConflictDataSize));

	/* 已完成可串行化事务的列表头。 */
	fc_size = add_size(fc_size, sizeof(SHM_QUEUE));

	/* 用于 SLRU 跟踪旧提交的 xids 的共享内存结构。 */
	fc_size = add_size(fc_size, sizeof(SerialControlData));
	fc_size = add_size(fc_size, SimpleLruShmemSize(NUM_SERIAL_BUFFERS, 0));

	return fc_size;
}


/*
 * 计算与 PREDICATELOCKTAG 关联的哈希代码。
 *
 * 由于我们希望对 PREDICATELOCKTARGET 和 PREDICATELOCK 哈希表使用同一组分区锁，
 * 我们必须确保 PREDICATELOCK 落入与其关联的 PREDICATELOCKTARGETs 相同的分区编号。
 * dynahash.c 期望分区编号是哈希代码的低位，因此 PREDICATELOCKTAG 的哈希代码
 * 必须与关联的 PREDICATELOCKTARGETTAG 的哈希代码具有相同的低位。我们通过这种
 * 专用哈希函数来实现这一点。
 */
static uint32 fc_predicatelock_hash(const void *fc_key, Size fc_keysize)
{
	const PREDICATELOCKTAG *fc_predicatelocktag = (const PREDICATELOCKTAG *) fc_key;
	uint32		fc_targethash;

	Assert(fc_keysize == sizeof(PREDICATELOCKTAG));

	/* 查找关联的目标对象，并计算其哈希代码 */
	fc_targethash = PredicateLockTargetTagHashCode(&fc_predicatelocktag->myTarget->tag);

	return PredicateLockHashCodeFromTargetHashCode(fc_predicatelocktag, fc_targethash);
}


/*
 * GetPredicateLockStatusData
 *		返回一个表，包含谓词锁管理器的内部状态，以供 pg_lock_status 使用。
 *
 * 与 GetLockStatusData 一样，这个函数试图尽可能短地保持分区 LWLocks，通过返回两个数组
 * 来实现，这两个数组简单地包含每个锁表条目的 PREDICATELOCKTARGETTAG 和 SERIALIZABLEXACT。
 * 相同的 PREDICATELOCKTARGETTAG 和 SERIALIZABLEXACT 的多个副本可能会出现。
 */
PredicateLockData *
GetPredicateLockStatusData(void)
{
	PredicateLockData *fc_data;
	int			fc_i;
	int			fc_els,
				fc_el;
	HASH_SEQ_STATUS fc_seqstat;
	PREDICATELOCK *fc_predlock;

	fc_data = (PredicateLockData *) palloc(sizeof(PredicateLockData));

	/*
	 * 为确保一致性，按升序同时锁定所有分区锁，然后是 SerializableXactHashLock。
	 */
	for (fc_i = 0; fc_i < NUM_PREDICATELOCK_PARTITIONS; fc_i++)
		LWLockAcquire(PredicateLockHashPartitionLockByIndex(fc_i), LW_SHARED);
	LWLockAcquire(SerializableXactHashLock, LW_SHARED);

	/* 获取锁的数量并分配适当大小的数组。 */
	fc_els = hash_get_num_entries(PredicateLockHash);
	fc_data->nelements = fc_els;
	fc_data->locktags = (PREDICATELOCKTARGETTAG *)
		palloc(sizeof(PREDICATELOCKTARGETTAG) * fc_els);
	fc_data->xacts = (SERIALIZABLEXACT *)
		palloc(sizeof(SERIALIZABLEXACT) * fc_els);


	/* 扫描 PredicateLockHash 并复制内容 */
	hash_seq_init(&fc_seqstat, PredicateLockHash);

	fc_el = 0;

	while ((fc_predlock = (PREDICATELOCK *) hash_seq_search(&fc_seqstat)))
	{
		fc_data->locktags[fc_el] = fc_predlock->tag.myTarget->tag;
		fc_data->xacts[fc_el] = *fc_predlock->tag.myXact;
		fc_el++;
	}

	Assert(fc_el == fc_els);

	/* 以相反的顺序释放锁 */
	LWLockRelease(SerializableXactHashLock);
	for (fc_i = NUM_PREDICATELOCK_PARTITIONS - 1; fc_i >= 0; fc_i--)
		LWLockRelease(PredicateLockHashPartitionLockByIndex(fc_i));

	return fc_data;
}

/*
 * 通过将最旧的 sxact（位于 SummarizeOldestCommittedSxact 队列前面的一个）推入摘要形式，
 * 来释放共享内存结构。每次调用将释放一个 SERIALIZABLEXACT 结构，并可能还会释放一个
 * 或多个这些结构：SERIALIZABLEXID、PREDICATELOCK、PREDICATELOCKTARGET、RWConflictData。
 */
static void fc_SummarizeOldestCommittedSxact(void)
{
	SERIALIZABLEXACT *fc_sxact;

	LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE);

	/*
	 * 只有在没有 sxact 插槽可用时，这个函数才会被调用。
	 * 其中一些必须属于旧的、已经完成的事务，因此在 FinishedSerializableTransactions 列表中
	 * 应该有我们可以总结的内容。然而，这里存在竞争条件：在我们没有持有任何锁的情况下，
	 * 一笔事务可能已经结束并清理了所有已完成的 sxact 条目，从而释放了它们的 sxact 插槽。
	 * 在这种情况下，我们这里没有任何事情可做。调用者将在重试时找到其他后端释放的
	 * 插槽。
	 */
	if (SHMQueueEmpty(FinishedSerializableTransactions))
	{
		LWLockRelease(SerializableFinishedListLock);
		return;
	}

	/*
	 * 从已完成列表中获取第一个 sxact —— 这将是最早的提交。
	 * 从列表中删除它。
	 */
	fc_sxact = (SERIALIZABLEXACT *)
		SHMQueueNext(FinishedSerializableTransactions,
					 FinishedSerializableTransactions,
					 offsetof(SERIALIZABLEXACT, finishedLink));
	SHMQueueDelete(&(fc_sxact->finishedLink));

	/* 添加到 SLRU 摘要信息中。 */
	if (TransactionIdIsValid(fc_sxact->topXid) && !SxactIsReadOnly(fc_sxact))
		fc_SerialAdd(fc_sxact->topXid, SxactHasConflictOut(fc_sxact)
				  ? fc_sxact->SeqNo.earliestOutConflictCommit : InvalidSerCommitSeqNo);

	/* 总结并释放细节。 */
	fc_ReleaseOneSerializableXact(fc_sxact, false, true);

	LWLockRelease(SerializableFinishedListLock);
}

/*
 * GetSafeSnapshot
 *		获取并注册一个用于只读可延迟事务的快照。确保快照是“安全的”，
 *		即在其上运行的只读事务可以在没有进一步检查的情况下可串行执行。
 *		这需要等待并发事务完成，如果其中一个可能会产生冲突，则重试并使用新快照。
 *
 *		如同 GetSerializableTransactionSnapshot（这是一个子例程），
 *		传入的 Snapshot 指针应引用可以安全传递给 GetSnapshotData 的静态数据区域。
 */
static Snapshot fc_GetSafeSnapshot(Snapshot fc_origSnapshot)
{
	Snapshot	fc_snapshot;

	Assert(XactReadOnly && XactDeferrable);

	while (true)
	{
		/*
		 * GetSerializableTransactionSnapshotInt 将调用 GetSnapshotData，
		 * 因此我们需要提供调用者传递给我们的静态快照区域。
		 * 返回的指针实际上是传递给它的同一个指针，但我们这里避免假设这一点。
		 */
		fc_snapshot = fc_GetSerializableTransactionSnapshotInt(fc_origSnapshot,
														 NULL, InvalidPid);

		if (MySerializableXact == InvalidSerializableXact)
			return fc_snapshot;	/* 没有并发的读写事务；这是安全的 */

		LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

		/*
		 * 等待并发事务完成。如果其中一个将我们标记为冲突，则提前停止。
		 */
		MySerializableXact->flags |= SXACT_FLAG_DEFERRABLE_WAITING;
		while (!(SHMQueueEmpty(&MySerializableXact->possibleUnsafeConflicts) ||
				 SxactIsROUnsafe(MySerializableXact)))
		{
			LWLockRelease(SerializableXactHashLock);
			ProcWaitForSignal(WAIT_EVENT_SAFE_SNAPSHOT);
			LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
		}
		MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING;

		if (!SxactIsROUnsafe(MySerializableXact))
		{
			LWLockRelease(SerializableXactHashLock);
			break;				/* 成功 */
		}

		LWLockRelease(SerializableXactHashLock);

		/* 否则，需要重试... */
		ereport(DEBUG2,
				(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
				 errmsg_internal("deferrable snapshot was unsafe; trying a new one")));
		ReleasePredicateLocks(false, false);
	}

	/*
	 * 现在我们有了一个安全的快照，因此不需要进行任何进一步的检查。
	 */
	Assert(SxactIsROSafe(MySerializableXact));
	ReleasePredicateLocks(false, true);

	return fc_snapshot;
}

/*
 * GetSafeSnapshotBlockingPids
 *		如果指定的进程在 GetSafeSnapshot 中目前被阻塞，
 *		将所有阻塞它的进程的进程 ID 写入调用者提供的输出缓冲区 output[]。
 *		列表在 output_size 处截断，写入缓冲区的 PID 数量作为返回值。
 *		如果给定的 PID 当前未在 GetSafeSnapshot 中被阻塞，则返回零。
 */
int GetSafeSnapshotBlockingPids(int fc_blocked_pid, int *fc_output, int fc_output_size)
{
	int			fc_num_written = 0;
	SERIALIZABLEXACT *fc_sxact;

	LWLockAcquire(SerializableXactHashLock, LW_SHARED);

	/* 通过线性搜索查找被阻塞的 pid 的 SERIALIZABLEXACT。 */
	for (fc_sxact = fc_FirstPredXact(); fc_sxact != NULL; fc_sxact = fc_NextPredXact(fc_sxact))
	{
		if (fc_sxact->pid == fc_blocked_pid)
			break;
	}

	/* 我们找到了它吗？它目前在 GetSafeSnapshot 中等待吗？ */
	if (fc_sxact != NULL && SxactIsDeferrableWaiting(fc_sxact))
	{
		RWConflict	fc_possibleUnsafeConflict;

		/* 遍历可能不安全的冲突列表以收集 PID。 */
		fc_possibleUnsafeConflict = (RWConflict)
			SHMQueueNext(&fc_sxact->possibleUnsafeConflicts,
						 &fc_sxact->possibleUnsafeConflicts,
						 offsetof(RWConflictData, inLink));

		while (fc_possibleUnsafeConflict != NULL && fc_num_written < fc_output_size)
		{
			fc_output[fc_num_written++] = fc_possibleUnsafeConflict->sxactOut->pid;
			fc_possibleUnsafeConflict = (RWConflict)
				SHMQueueNext(&fc_sxact->possibleUnsafeConflicts,
							 &fc_possibleUnsafeConflict->inLink,
							 offsetof(RWConflictData, inLink));
		}
	}

	LWLockRelease(SerializableXactHashLock);

	return fc_num_written;
}

/*
 * 获取可以用于当前事务的快照。
 *
 * 确保我们在 MySerializableXact 中有一个 SERIALIZABLEXACT 引用。
 * 它应为该进程提供当前状态，且包含在 PredXact 中。
 *
 * 传入的 Snapshot 指针应引用可以安全传递给 GetSnapshotData 的静态数据区域。
 * 返回值实际上始终是同一个指针；在此函数中不会分配新的快照数据结构。
 */
Snapshot GetSerializableTransactionSnapshot(Snapshot fc_snapshot)
{
	Assert(IsolationIsSerializable());

	/*
	 * 在恢复仍处于活动状态时无法使用可串行化模式，例如，
	 * 在热备份时。即使在 check_XactIsoLevel() 中进行了检查，如果
	 * default_transaction_isolation 设置为可串行化，则我们也可能到达这里，
	 * 因此相应地表述提示。
	 */
	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot use serializable mode in a hot standby"),
				 errdetail("\"default_transaction_isolation\" is set to \"serializable\"."),
				 errhint("You can use \"SET default_transaction_isolation = 'repeatable read'\" to change the default.")));

	/*
	 * 对于 SERIALIZABLE READ ONLY DEFERRABLE 事务，提供了一种特殊优化——
	 * 我们可以等待合适的快照，从而避免在其运行时的所有 SSI 开销。
	 */
	if (XactReadOnly && XactDeferrable)
		return fc_GetSafeSnapshot(fc_snapshot);

	return fc_GetSerializableTransactionSnapshotInt(fc_snapshot,
												 NULL, InvalidPid);
}

/*
 * 导入用于当前事务的快照。
 *
 * 这几乎与 GetSerializableTransactionSnapshot 相同，只是我们
 * 不会获得一个新的快照，而是使用我们所获得的数据。
 *
 * 调用者必须验证快照来自于可串行化的事务；如果我们是读写的，
 * 源事务必须不是只读的。
 */
void SetSerializableTransactionSnapshot(Snapshot fc_snapshot,
								   VirtualTransactionId *fc_sourcevxid,
								   int fc_sourcepid)
{
	Assert(IsolationIsSerializable());

	
/*
	 * 如果这是由parallel.c中的并行工作程序调用的，我们不想
	 * 立即创建一个SERIALIZABLEXACT，因为领导者的
	 * SERIALIZABLEXACT将通过AttachSerializableXact()安装。我们
	 * 也不想在这种情况下拒绝SERIALIZABLE READ ONLY DEFERRABLE，
	 * 因为领导者已经确定它传给我们的快照是安全的。因此我们没有
	 * 任何事情需要做。
	 */
	if (IsParallelWorker())
		return;

	/*
	 * 我们不允许SERIALIZABLE READ ONLY DEFERRABLE事务
	 * 导入快照，因为当我们使用被告知的快照时，没有办法等待
	 * 一个安全的快照。 （XXX，除了引发错误外，
	 * 我们可以忽略XactDeferrable标志吗？）
	 */
	if (XactReadOnly && XactDeferrable)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("a snapshot-importing transaction must not be READ ONLY DEFERRABLE")));

	(void) fc_GetSerializableTransactionSnapshotInt(fc_snapshot, fc_sourcevxid,
												 fc_sourcepid);
}

/*
 * GetSerializableTransactionSnapshot的核心部分
 *
 * 如果sourcevxid是有效的，这实际上是一个导入操作，我们应该
 * 跳过调用GetSnapshotData，因为快照内容已经加载。
 * 然而：为了避免竞争条件，我们必须检查
 * 在获取SerializableXactHashLock后源事务是否仍在运行。
 * 我们通过调用ProcArrayInstallImportedXmin来做到这一点。
 */
static Snapshot fc_GetSerializableTransactionSnapshotInt(Snapshot fc_snapshot,
									  VirtualTransactionId *fc_sourcevxid,
									  int fc_sourcepid)
{
	PGPROC	   *fc_proc;
	VirtualTransactionId fc_vxid;
	SERIALIZABLEXACT *fc_sxact,
			   *fc_othersxact;

	/* 我们只对可序列化事务执行此操作。一次。 */
	Assert(MySerializableXact == InvalidSerializableXact);

	Assert(!RecoveryInProgress());

	/*
	 * 由于可序列化事务的所有部分必须使用相同的
	 * 快照，因此在并行操作开始后再建立一个已经太晚了。
	 */
	if (IsInParallelMode())
		elog(ERROR, "cannot establish serializable snapshot during a parallel operation");

	fc_proc = MyProc;
	Assert(fc_proc != NULL);
	GET_VXID_FROM_PGPROC(fc_vxid, *fc_proc);

	/*
	 * 首先我们获取sxact结构，这可能涉及循环和访问
	 * “已完成”列表以释放一个结构供使用。
	 *
	 * 我们必须在获取/检查快照时持有SerializableXactHashLock
	 * 以避免竞争条件，原因与GetSnapshotData持有ProcArrayLock相似。
	 * 由于我们可能需要释放SerializableXactHashLock以调用
	 * SummarizeOldestCommittedSxact，这意味着我们必须首先创建sxact，
	 * 这有点麻烦（特别是procarray.c中的elog(ERROR)会导致我们泄漏
	 * sxact）。考虑重构以避免这种情况。
	 */
#ifdef TEST_SUMMARIZE_SERIAL
	SummarizeOldestCommittedSxact();
#endif
	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
	do
	{
		fc_sxact = fc_CreatePredXact();
		/* 如果为null，将已提交的sxact推送到SLRU摘要并重试。 */
		if (!fc_sxact)
		{
			LWLockRelease(SerializableXactHashLock);
			fc_SummarizeOldestCommittedSxact();
			LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
		}
	} while (!fc_sxact);

	/* 获取快照，或检查它是否安全使用 */
	if (!fc_sourcevxid)
		fc_snapshot = GetSnapshotData(fc_snapshot);
	else if (!ProcArrayInstallImportedXmin(fc_snapshot->xmin, fc_sourcevxid))
	{
		fc_ReleasePredXact(fc_sxact);
		LWLockRelease(SerializableXactHashLock);
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("could not import the requested snapshot"),
				 errdetail("The source process with PID %d is not running anymore.",
						   fc_sourcepid)));
	}

	/*
	 * 如果没有不只读的可序列化事务，我们可以“选择退出”
	 * 针对只读事务的谓词锁定和冲突检查。
	 *
	 * 这样做是安全的原因在于，只读事务只能在
	 * 其与一个可写事务重叠的情况下成为一个危险结构的一部分，而
	 * 该可写事务又与一个在只读事务开始之前提交的
	 * 可写事务重叠。一个新的可写事务可以与这个重叠，
	 * 但是它不能满足重叠一个在这个事务开始之前提交的事务的
	 * 另一个条件。
	 */
	if (XactReadOnly && PredXact->WritableSxactCount == 0)
	{
		fc_ReleasePredXact(fc_sxact);
		LWLockRelease(SerializableXactHashLock);
		return fc_snapshot;
	}

	/* 初始化结构。 */
	fc_sxact->vxid = fc_vxid;
	fc_sxact->SeqNo.lastCommitBeforeSnapshot = PredXact->LastSxactCommitSeqNo;
	fc_sxact->prepareSeqNo = InvalidSerCommitSeqNo;
	fc_sxact->commitSeqNo = InvalidSerCommitSeqNo;
	SHMQueueInit(&(fc_sxact->outConflicts));
	SHMQueueInit(&(fc_sxact->inConflicts));
	SHMQueueInit(&(fc_sxact->possibleUnsafeConflicts));
	fc_sxact->topXid = GetTopTransactionIdIfAny();
	fc_sxact->finishedBefore = InvalidTransactionId;
	fc_sxact->xmin = fc_snapshot->xmin;
	fc_sxact->pid = MyProcPid;
	fc_sxact->pgprocno = MyProc->pgprocno;
	SHMQueueInit(&(fc_sxact->predicateLocks));
	SHMQueueElemInit(&(fc_sxact->finishedLink));
	fc_sxact->flags = 0;
	if (XactReadOnly)
	{
		fc_sxact->flags |= SXACT_FLAG_READ_ONLY;

		/*
		 * 注册所有并发的读/写事务作为可能的冲突；如果
		 * 所有这些事务在没有与早期
		 * 事务的任何输出冲突的情况下提交，则此快照可以被视为安全的（我们可以在
		 * 不跟踪谓词锁的情况下运行）。
		 */
		for (fc_othersxact = fc_FirstPredXact();
			 fc_othersxact != NULL;
			 fc_othersxact = fc_NextPredXact(fc_othersxact))
		{
			if (!SxactIsCommitted(fc_othersxact)
				&& !SxactIsDoomed(fc_othersxact)
				&& !SxactIsReadOnly(fc_othersxact))
			{
				fc_SetPossibleUnsafeConflict(fc_sxact, fc_othersxact);
			}
		}

		/*
		 * 如果我们没有发现任何可能的不安全冲突，因为每个
		 * 未提交的可写事务结果都是注定要失败的，那么我们
		 * 可以立即“选择退出”。请参见前面关于
		 * PredXact->WritableSxactCount == 0 的检查注释。
		 */
		if (SHMQueueEmpty(&fc_sxact->possibleUnsafeConflicts))
		{
			fc_ReleasePredXact(fc_sxact);
			LWLockRelease(SerializableXactHashLock);
			return fc_snapshot;
		}
	}
	else
	{
		++(PredXact->WritableSxactCount);
		Assert(PredXact->WritableSxactCount <=
			   (MaxBackends + max_prepared_xacts));
	}

	/* 维护可串行化的全局 xmin 信息。 */
	if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
	{
		Assert(PredXact->SxactGlobalXminCount == 0);
		PredXact->SxactGlobalXmin = fc_snapshot->xmin;
		PredXact->SxactGlobalXminCount = 1;
		fc_SerialSetActiveSerXmin(fc_snapshot->xmin);
	}
	else if (TransactionIdEquals(fc_snapshot->xmin, PredXact->SxactGlobalXmin))
	{
		Assert(PredXact->SxactGlobalXminCount > 0);
		PredXact->SxactGlobalXminCount++;
	}
	else
	{
		Assert(TransactionIdFollows(fc_snapshot->xmin, PredXact->SxactGlobalXmin));
	}

	MySerializableXact = fc_sxact;
	MyXactDidWrite = false;		/* 还没有写入任何内容 */

	LWLockRelease(SerializableXactHashLock);

	fc_CreateLocalPredicateLockHash();

	return fc_snapshot;
}

static void fc_CreateLocalPredicateLockHash(void)
{
	HASHCTL		fc_hash_ctl;

	/* 初始化后端本地父锁的哈希表 */
	Assert(LocalPredicateLockHash == NULL);
	fc_hash_ctl.keysize = sizeof(PREDICATELOCKTARGETTAG);
	fc_hash_ctl.entrysize = sizeof(LOCALPREDICATELOCK);
	LocalPredicateLockHash = hash_create("Local predicate lock",
										 max_predicate_locks_per_xact,
										 &fc_hash_ctl,
										 HASH_ELEM | HASH_BLOBS);
}

/*
 * 在 SerializableXidHash 中注册顶级 XID。
 * 另外在 MySerializableXact 中存储以便于引用。
 */
void RegisterPredicateLockingXid(TransactionId fc_xid)
{
	SERIALIZABLEXIDTAG fc_sxidtag;
	SERIALIZABLEXID *fc_sxid;
	bool		fc_found;

	/*
	 * 如果我们没有跟踪此事务的谓词锁数据，
	 * 我们应该忽略该请求并快速返回。
	 */
	if (MySerializableXact == InvalidSerializableXact)
		return;

	/* 我们应该有一个有效的 XID，并且处于顶层。 */
	Assert(TransactionIdIsValid(fc_xid));

	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

	/* 每个事务只应执行一次。 */
	Assert(MySerializableXact->topXid == InvalidTransactionId);

	MySerializableXact->topXid = fc_xid;

	fc_sxidtag.xid = fc_xid;
	fc_sxid = (SERIALIZABLEXID *) hash_search(SerializableXidHash,
										   &fc_sxidtag,
										   HASH_ENTER, &fc_found);
	Assert(!fc_found);

	/* 初始化结构。 */
	fc_sxid->myXact = MySerializableXact;
	LWLockRelease(SerializableXactHashLock);
}


/*
 * 检查是否有任何事务对给定块号的页面持有任何谓词锁。
 *
 * 注意，事务可能已完成但由于重叠的可串行化事务尚未进行
 * 清理。无论事务隔离级别如何，这都必须返回有效的信息。
 *
 * 另请注意，这并不检查冲突的关系锁，
 * 只是特定于给定页面的锁。
 *
 * 一个用途是在 GiST 索引真空期间支持正确的行为。
 */
bool PageIsPredicateLocked(Relation fc_relation, BlockNumber fc_blkno)
{
	PREDICATELOCKTARGETTAG fc_targettag;
	uint32		fc_targettaghash;
	LWLock	   *fc_partitionLock;
	PREDICATELOCKTARGET *fc_target;

	SET_PREDICATELOCKTARGETTAG_PAGE(fc_targettag,
									fc_relation->rd_node.dbNode,
									fc_relation->rd_id,
									fc_blkno);

	fc_targettaghash = PredicateLockTargetTagHashCode(&fc_targettag);
	fc_partitionLock = PredicateLockHashPartitionLock(fc_targettaghash);
	LWLockAcquire(fc_partitionLock, LW_SHARED);
	fc_target = (PREDICATELOCKTARGET *)
		hash_search_with_hash_value(PredicateLockTargetHash,
									&fc_targettag, fc_targettaghash,
									HASH_FIND, NULL);
	LWLockRelease(fc_partitionLock);

	return (fc_target != NULL);
}


/*
 * 检查此事务是否持有特定锁。
 *
 * 重要说明：此函数可能返回 false，即使锁被持有，因为它使用
 * 本地锁表，如果另一个事务修改我们的锁列表（例如，拆分索引页面），
 * 本地锁表不会更新。当一个更粗粒度的锁覆盖该目标时，它也可能返回 true。
 * 使用此函数时请小心，确保此类错误是可以接受的！
 */
static bool fc_PredicateLockExists(const PREDICATELOCKTARGETTAG *fc_targettag)
{
	LOCALPREDICATELOCK *fc_lock;

	/* 检查本地哈希表 */
	fc_lock = (LOCALPREDICATELOCK *) hash_search(LocalPredicateLockHash,
											  fc_targettag,
											  HASH_FIND, NULL);

	if (!fc_lock)
		return false;

	/*
	 * 在表中找到条目，但仍需检查它是否实际上被持有 -- 它可能只是某个持有锁的父项。
	 */
	return fc_lock->held;
}

/*
 * 返回锁层次结构中的父锁标记：覆盖提供的标记的下一个更粗的锁。
 *
 * 如果存在返回 true 并将 *parent 设置为父标记，
 * 如果不存在则返回 false。
 */
static bool fc_GetParentPredicateLockTag(const PREDICATELOCKTARGETTAG *fc_tag,
						  PREDICATELOCKTARGETTAG *fc_parent)
{
	switch (GET_PREDICATELOCKTARGETTAG_TYPE(*fc_tag))
	{
		case PREDLOCKTAG_RELATION:
			/* 关系锁没有父锁 */
			return false;

		case PREDLOCKTAG_PAGE:
			/* 父锁是关系锁 */
			SET_PREDICATELOCKTARGETTAG_RELATION(*fc_parent,
												GET_PREDICATELOCKTARGETTAG_DB(*fc_tag),
												GET_PREDICATELOCKTARGETTAG_RELATION(*fc_tag));

			return true;

		case PREDLOCKTAG_TUPLE:
			/* 父锁是页面锁 */
			SET_PREDICATELOCKTARGETTAG_PAGE(*fc_parent,
											GET_PREDICATELOCKTARGETTAG_DB(*fc_tag),
											GET_PREDICATELOCKTARGETTAG_RELATION(*fc_tag),
											GET_PREDICATELOCKTARGETTAG_PAGE(*fc_tag));
			return true;
	}

	/* 不可达 */
	Assert(false);
	return false;
}

/*
 * 检查我们正在考虑的锁是否已被我们事务的
 * 更粗的锁覆盖。
 *
 * 与 PredicateLockExists 类似，此函数可能返回虚假
 * 否定，但不会返回虚假
 * 肯定。
 */
static bool fc_CoarserLockCovers(const PREDICATELOCKTARGETTAG *fc_newtargettag)
{
	PREDICATELOCKTARGETTAG fc_targettag,
				fc_parenttag;

	fc_targettag = *fc_newtargettag;

	/* 迭代检查父项直到没有更多 */
	while (fc_GetParentPredicateLockTag(&fc_targettag, &fc_parenttag))
	{
		fc_targettag = fc_parenttag;
		if (fc_PredicateLockExists(&fc_targettag))
			return true;
	}

	/* 没有更多父项可检查；锁没有被覆盖 */
	return false;
}

/*
 * 从谓词锁目标哈希中移除虚拟条目，以释放一些
 * 临时空间。调用者必须持有 SerializablePredicateListLock，
 * 并且必须在释放锁之前使用 RestoreScratchTarget() 恢复该条目。
 *
 * 如果 lockheld 为 true，调用者已经持有包含
 * 临时条目的分区锁。
 */
static void fc_RemoveScratchTarget(bool fc_lockheld)
{
	bool		fc_found;

	Assert(LWLockHeldByMe(SerializablePredicateListLock));

	if (!fc_lockheld)
		LWLockAcquire(ScratchPartitionLock, LW_EXCLUSIVE);
	hash_search_with_hash_value(PredicateLockTargetHash,
								&ScratchTargetTag,
								ScratchTargetTagHash,
								HASH_REMOVE, &fc_found);
	Assert(fc_found);
	if (!fc_lockheld)
		LWLockRelease(ScratchPartitionLock);
}

/*
 * 在谓词锁目标哈希中重新插入虚拟条目。
 */
static void fc_RestoreScratchTarget(bool fc_lockheld)
{
	bool		fc_found;

	Assert(LWLockHeldByMe(SerializablePredicateListLock));

	if (!fc_lockheld)
		LWLockAcquire(ScratchPartitionLock, LW_EXCLUSIVE);
	hash_search_with_hash_value(PredicateLockTargetHash,
								&ScratchTargetTag,
								ScratchTargetTagHash,
								HASH_ENTER, &fc_found);
	Assert(!fc_found);
	if (!fc_lockheld)
		LWLockRelease(ScratchPartitionLock);
}

/*
 * 检查针对谓词锁目标的相关谓词锁列表是否为空，如果为空则移除该目标。
 */
static void fc_RemoveTargetIfNoLongerUsed(PREDICATELOCKTARGET *fc_target, uint32 fc_targettaghash)
{
	PREDICATELOCKTARGET *fc_rmtarget PG_USED_FOR_ASSERTS_ONLY;

	Assert(LWLockHeldByMe(SerializablePredicateListLock));

	/* 直到没有锁在此目标上才能移除。 */
	if (!SHMQueueEmpty(&fc_target->predicateLocks))
		return;

	/* 实际上移除目标。 */
	fc_rmtarget = hash_search_with_hash_value(PredicateLockTargetHash,
										   &fc_target->tag,
										   fc_targettaghash,
										   HASH_REMOVE, NULL);
	Assert(fc_rmtarget == fc_target);
}

/*
 * 删除由此进程拥有的子目标锁。
 * 此实现假设每个目标标签字段的使用是统一的。在不必要的情况下
 * 不必将其做得过于复杂。
 *
 * 在并行模式下，我们会获取 LWLock，因为工作
 * 后端可以访问领导者的 SERIALIZABLEXACT。否则，
 * 除非我们要修改它们，我们不会为与此事务相关的谓词锁或
 * 锁目标结构获取 LWLocks，因为没有其他进程被允许修改我们的
 * 锁。
 */
static void fc_DeleteChildTargetLocks(const PREDICATELOCKTARGETTAG *fc_newtargettag)
{
	SERIALIZABLEXACT *fc_sxact;
	PREDICATELOCK *fc_predlock;

	LWLockAcquire(SerializablePredicateListLock, LW_SHARED);
	fc_sxact = MySerializableXact;
	if (IsInParallelMode())
		LWLockAcquire(&fc_sxact->perXactPredicateListLock, LW_EXCLUSIVE);
	fc_predlock = (PREDICATELOCK *)
		SHMQueueNext(&(fc_sxact->predicateLocks),
					 &(fc_sxact->predicateLocks),
					 offsetof(PREDICATELOCK, xactLink));
	while (fc_predlock)
	{
		SHM_QUEUE  *fc_predlocksxactlink;
		PREDICATELOCK *fc_nextpredlock;
		PREDICATELOCKTAG fc_oldlocktag;
		PREDICATELOCKTARGET *fc_oldtarget;
		PREDICATELOCKTARGETTAG fc_oldtargettag;

		fc_predlocksxactlink = &(fc_predlock->xactLink);
		fc_nextpredlock = (PREDICATELOCK *)
			SHMQueueNext(&(fc_sxact->predicateLocks),
						 fc_predlocksxactlink,
						 offsetof(PREDICATELOCK, xactLink));

		fc_oldlocktag = fc_predlock->tag;
		Assert(fc_oldlocktag.myXact == fc_sxact);
		fc_oldtarget = fc_oldlocktag.myTarget;
		fc_oldtargettag = fc_oldtarget->tag;

		if (TargetTagIsCoveredBy(fc_oldtargettag, *fc_newtargettag))
		{
			uint32		fc_oldtargettaghash;
			LWLock	   *fc_partitionLock;
			PREDICATELOCK *fc_rmpredlock PG_USED_FOR_ASSERTS_ONLY;

			fc_oldtargettaghash = PredicateLockTargetTagHashCode(&fc_oldtargettag);
			fc_partitionLock = PredicateLockHashPartitionLock(fc_oldtargettaghash);

			LWLockAcquire(fc_partitionLock, LW_EXCLUSIVE);

			SHMQueueDelete(fc_predlocksxactlink);
			SHMQueueDelete(&(fc_predlock->targetLink));
			fc_rmpredlock = hash_search_with_hash_value
				(PredicateLockHash,
				 &fc_oldlocktag,
				 PredicateLockHashCodeFromTargetHashCode(&fc_oldlocktag,
														 fc_oldtargettaghash),
				 HASH_REMOVE, NULL);
			Assert(fc_rmpredlock == fc_predlock);

			fc_RemoveTargetIfNoLongerUsed(fc_oldtarget, fc_oldtargettaghash);

			LWLockRelease(fc_partitionLock);

			fc_DecrementParentLocks(&fc_oldtargettag);
		}

		fc_predlock = fc_nextpredlock;
	}
	if (IsInParallelMode())
		LWLockRelease(&fc_sxact->perXactPredicateListLock);
	LWLockRelease(SerializablePredicateListLock);
}

/*
 * 返回给定谓词锁目标的提升限制。这是
 * 在提升到指定标签之前允许的最大后代锁定数。请注意，限制包括非直接后代（例如，关系锁的元组和页面）。
 *
 * 目前，页面锁的默认限制为2，关系锁的限制为max_pred_locks_per_transaction - 1的一半，以匹配先前版本的行为进行升级。
 *
 * TODO SSI：我们可能应该添加额外的GUC，以允许基于关系中的页面的页面和元组锁的最大比率，以及页面中元组锁与元组的最大比率。这将在绝对数量限制的同时，提供更均衡的锁分配到最有用的地方，防止一个关系占用所有谓词锁资源。
 */
static int fc_MaxPredicateChildLocks(const PREDICATELOCKTARGETTAG *fc_tag)
{
	switch (GET_PREDICATELOCKTARGETTAG_TYPE(*fc_tag))
	{
		case PREDLOCKTAG_RELATION:
			return max_predicate_locks_per_relation < 0
				? (max_predicate_locks_per_xact
				   / (-max_predicate_locks_per_relation)) - 1
				: max_predicate_locks_per_relation;

		case PREDLOCKTAG_PAGE:
			return max_predicate_locks_per_page;

		case PREDLOCKTAG_TUPLE:

			/*
			 * 不可达：没有比元组更细粒度的粒度，因此我们
			 * 不应尝试提升到它。
			 */
			Assert(false);
			return 0;
	}

	/* 不可达 */
	Assert(false);
	return 0;
}

/*
 * 对新获得的谓词锁的所有祖先进行遍历，增加
 * 它们在父哈希表中的子级计数。如果它们中的任何一个有
 * 超过其提升阈值的后代，则获取
 * 最粗糙的此类锁。
 *
 * 如果获得了父锁，则返回true，否则返回false。
 */
static bool fc_CheckAndPromotePredicateLockRequest(const PREDICATELOCKTARGETTAG *fc_reqtag)
{
	PREDICATELOCKTARGETTAG fc_targettag,
				fc_nexttag,
				fc_promotiontag;
	LOCALPREDICATELOCK *fc_parentlock;
	bool		fc_found,
				fc_promote;

	fc_promote = false;

	fc_targettag = *fc_reqtag;

	/* 迭代检查父级 */
	while (fc_GetParentPredicateLockTag(&fc_targettag, &fc_nexttag))
	{
		fc_targettag = fc_nexttag;
		fc_parentlock = (LOCALPREDICATELOCK *) hash_search(LocalPredicateLockHash,
														&fc_targettag,
														HASH_ENTER,
														&fc_found);
		if (!fc_found)
		{
			fc_parentlock->held = false;
			fc_parentlock->childLocks = 1;
		}
		else
			fc_parentlock->childLocks++;

		if (fc_parentlock->childLocks >
			fc_MaxPredicateChildLocks(&fc_targettag))
		{
			/*
			 * 我们应该提升到这个父锁。然而，继续检查它的
			 * 祖先，以便正确获取它们的子级计数，并检查我们是否应该继续提升到其中一个。
			 */
			fc_promotiontag = fc_targettag;
			fc_promote = true;
		}
	}

	if (fc_promote)
	{
		/* 获取最粗糙的符合提升条件的祖先 */
		fc_PredicateLockAcquire(&fc_promotiontag);
		return true;
	}
	else
		return false;
}

/*
 * 释放锁时，减少所有祖先锁的子级计数。
 *
 * 仅在通过
 * DeleteChildTargetLocks释放锁时调用（即当锁因
 * 我们获得其父锁而变得冗余时，可能是由于提升）或当新的
 * MVCC写锁使谓词锁变得不必要时。因为在事务结束时释放锁时，这些信息不再需要，所以没有必要调用它。
 */
static void fc_DecrementParentLocks(const PREDICATELOCKTARGETTAG *fc_targettag)
{
	PREDICATELOCKTARGETTAG fc_parenttag,
				fc_nexttag;

	fc_parenttag = *fc_targettag;

	while (fc_GetParentPredicateLockTag(&fc_parenttag, &fc_nexttag))
	{
		uint32		fc_targettaghash;
		LOCALPREDICATELOCK *fc_parentlock,
				   *fc_rmlock PG_USED_FOR_ASSERTS_ONLY;

		fc_parenttag = fc_nexttag;
		fc_targettaghash = PredicateLockTargetTagHashCode(&fc_parenttag);
		fc_parentlock = (LOCALPREDICATELOCK *)
			hash_search_with_hash_value(LocalPredicateLockHash,
										&fc_parenttag, fc_targettaghash,
										HASH_FIND, NULL);

		/*
		 * 父锁在锁表中可能不存在的几率很小。这种情况可能
		 * 发生在我们因为索引分裂导致子引用计数错误而过早删除它。
		 */
		if (fc_parentlock == NULL)
			continue;

		fc_parentlock->childLocks--;

		/*
		 * 在类似情况下，父锁的引用计数可能为零。只有在我们
		 * 持有那个锁时才会发生这种情况（否则我们
		 * 会删除该条目）。
		 */
		if (fc_parentlock->childLocks < 0)
		{
			Assert(fc_parentlock->held);
			fc_parentlock->childLocks = 0;
		}

		if ((fc_parentlock->childLocks == 0) && (!fc_parentlock->held))
		{
			fc_rmlock = (LOCALPREDICATELOCK *)
				hash_search_with_hash_value(LocalPredicateLockHash,
											&fc_parenttag, fc_targettaghash,
											HASH_REMOVE, NULL);
			Assert(fc_rmlock == fc_parentlock);
		}
	}
}

/*
 * 指示指定事务持有在给定目标上的谓词锁。
 * 如果锁已被持有，则无效。
 *
 * 这会更新锁表和sxact的锁列表，并在必要时创建
 * 锁目标，但不做任何与粒度提升或本地锁表相关的事情。有关内容，请参见
 * PredicateLockAcquire。
 */
static void fc_CreatePredicateLock(const PREDICATELOCKTARGETTAG *fc_targettag,
					uint32 fc_targettaghash,
					SERIALIZABLEXACT *fc_sxact)
{
	PREDICATELOCKTARGET *fc_target;
	PREDICATELOCKTAG fc_locktag;
	PREDICATELOCK *fc_lock;
	LWLock	   *fc_partitionLock;
	bool		fc_found;

	fc_partitionLock = PredicateLockHashPartitionLock(fc_targettaghash);

	LWLockAcquire(SerializablePredicateListLock, LW_SHARED);
	if (IsInParallelMode())
		LWLockAcquire(&fc_sxact->perXactPredicateListLock, LW_EXCLUSIVE);
	LWLockAcquire(fc_partitionLock, LW_EXCLUSIVE);

	/* 确保目标被表示。 */
	fc_target = (PREDICATELOCKTARGET *)
		hash_search_with_hash_value(PredicateLockTargetHash,
									fc_targettag, fc_targettaghash,
									HASH_ENTER_NULL, &fc_found);
	if (!fc_target)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of shared memory"),
				 errhint("You might need to increase max_pred_locks_per_transaction.")));
	if (!fc_found)
		SHMQueueInit(&(fc_target->predicateLocks));

	/* 我们已获得sxact和目标，确保它们已联接。 */
	fc_locktag.myTarget = fc_target;
	fc_locktag.myXact = fc_sxact;
	fc_lock = (PREDICATELOCK *)
		hash_search_with_hash_value(PredicateLockHash, &fc_locktag,
									PredicateLockHashCodeFromTargetHashCode(&fc_locktag, fc_targettaghash),
									HASH_ENTER_NULL, &fc_found);
	if (!fc_lock)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of shared memory"),
				 errhint("You might need to increase max_pred_locks_per_transaction.")));

	if (!fc_found)
	{
		SHMQueueInsertBefore(&(fc_target->predicateLocks), &(fc_lock->targetLink));
		SHMQueueInsertBefore(&(fc_sxact->predicateLocks),
							 &(fc_lock->xactLink));
		fc_lock->commitSeqNo = InvalidSerCommitSeqNo;
	}

	LWLockRelease(fc_partitionLock);
	if (IsInParallelMode())
		LWLockRelease(&fc_sxact->perXactPredicateListLock);
	LWLockRelease(SerializablePredicateListLock);
}

/*
 * 如果当前连接尚未持有，则在指定目标上获取谓词锁。
 * 这会更新本地锁表，并使用它来实施粒度提升。如果需要，它将合并
 * 多个锁为一个较粗的锁，并释放任何被新锁覆盖的更细粒度锁。
 */
static void fc_PredicateLockAcquire(const PREDICATELOCKTARGETTAG *fc_targettag)
{
	uint32		fc_targettaghash;
	bool		fc_found;
	LOCALPREDICATELOCK *fc_locallock;

	/* 我们已经有锁，还是有覆盖锁？ */
	if (fc_PredicateLockExists(fc_targettag))
		return;

	if (fc_CoarserLockCovers(fc_targettag))
		return;

	/* 相同的哈希和LW锁适用于锁目标和本地锁。 */
	fc_targettaghash = PredicateLockTargetTagHashCode(fc_targettag);

	/* 在本地表中获取锁 */
	fc_locallock = (LOCALPREDICATELOCK *)
		hash_search_with_hash_value(LocalPredicateLockHash,
									fc_targettag, fc_targettaghash,
									HASH_ENTER, &fc_found);
	fc_locallock->held = true;
	if (!fc_found)
		fc_locallock->childLocks = 0;

	/* 实际创建锁 */
	fc_CreatePredicateLock(fc_targettag, fc_targettaghash, MySerializableXact);

	/*
	 * 锁已经被获取。检查它是否应该提升为
	 * 粗粒度，或是否存在需要清理的更细粒度锁。
	 */
	if (fc_CheckAndPromotePredicateLockRequest(fc_targettag))
	{
		/*
		 * 锁请求已提升到粗粒度锁，并且该
		 * 锁已被获取。它将删除该锁和任何其
		 * 子级，因此我们完成了。
		 */
	}
	else
	{
		/* 清理任何更细粒度的锁 */
		if (GET_PREDICATELOCKTARGETTAG_TYPE(*fc_targettag) != PREDLOCKTAG_TUPLE)
			fc_DeleteChildTargetLocks(fc_targettag);
	}
}


/*
 *		PredicateLockRelation
 *
 * 在关系级别获取谓词锁。
 * 如果不在完整的可序列化事务隔离级别，则跳过。
 * 如果这是一个临时表，则跳过。
 * 清除该会话在关系上拥有的任何更细粒度的谓词锁。
 */
void PredicateLockRelation(Relation fc_relation, Snapshot fc_snapshot)
{
	PREDICATELOCKTARGETTAG fc_tag;

	if (!fc_SerializationNeededForRead(fc_relation, fc_snapshot))
		return;

	SET_PREDICATELOCKTARGETTAG_RELATION(fc_tag,
										fc_relation->rd_node.dbNode,
										fc_relation->rd_id);
	fc_PredicateLockAcquire(&fc_tag);
}

/*
 *		PredicateLockPage
 *
 * 在页面级别获取谓词锁。
 * 如果不在完整的可序列化事务隔离级别，则跳过。
 * 如果这是一个临时表，则跳过。
 * 如果已经有更粗的谓词锁覆盖此页面，则跳过。
 * 清除该会话在关系上拥有的任何更细粒度的谓词锁。
 */
void PredicateLockPage(Relation fc_relation, BlockNumber fc_blkno, Snapshot fc_snapshot)
{
	PREDICATELOCKTARGETTAG fc_tag;

	if (!fc_SerializationNeededForRead(fc_relation, fc_snapshot))
		return;

	SET_PREDICATELOCKTARGETTAG_PAGE(fc_tag,
									fc_relation->rd_node.dbNode,
									fc_relation->rd_id,
									fc_blkno);
	fc_PredicateLockAcquire(&fc_tag);
}

/*
 *		PredicateLockTID
 *
 * 在元组级别获取谓词锁。
 * 如果不在完整的可序列化事务隔离级别，则跳过。
 * 如果这是一个临时表，则跳过。
 */
void PredicateLockTID(Relation fc_relation, ItemPointer fc_tid, Snapshot fc_snapshot,
				 TransactionId fc_tuple_xid)
{
	PREDICATELOCKTARGETTAG fc_tag;

	if (!fc_SerializationNeededForRead(fc_relation, fc_snapshot))
		return;

	
/*
	 * 返回该事务是否写入了它。
	 */
	if (fc_relation->rd_index == NULL)
	{
		/* 如果我们写入了；我们已经拥有写锁。 */
		if (TransactionIdIsCurrentTransactionId(fc_tuple_xid))
			return;
	}

	/*
	 * 首先快速但不决定性地测试关系锁。这将永远不会在关系未被锁定时导致返回，但偶尔会让检查继续，当确实存在关系级别锁时。
	 */
	SET_PREDICATELOCKTARGETTAG_RELATION(fc_tag,
										fc_relation->rd_node.dbNode,
										fc_relation->rd_id);
	if (fc_PredicateLockExists(&fc_tag))
		return;

	SET_PREDICATELOCKTARGETTAG_TUPLE(fc_tag,
									 fc_relation->rd_node.dbNode,
									 fc_relation->rd_id,
									 ItemPointerGetBlockNumber(fc_tid),
									 ItemPointerGetOffsetNumber(fc_tid));
	fc_PredicateLockAcquire(&fc_tag);
}


/*
 *		删除锁定目标
 *
 * 删除一个谓词锁定目标以及为其持有的任何锁。
 *
 * 调用者必须持有 SerializablePredicateListLock 和
 * 适当的目标哈希分区锁。
 */
static void fc_DeleteLockTarget(PREDICATELOCKTARGET *fc_target, uint32 fc_targettaghash)
{
	PREDICATELOCK *fc_predlock;
	SHM_QUEUE  *fc_predlocktargetlink;
	PREDICATELOCK *fc_nextpredlock;
	bool		fc_found;

	Assert(LWLockHeldByMeInMode(SerializablePredicateListLock,
								LW_EXCLUSIVE));
	Assert(LWLockHeldByMe(PredicateLockHashPartitionLock(fc_targettaghash)));

	fc_predlock = (PREDICATELOCK *)
		SHMQueueNext(&(fc_target->predicateLocks),
					 &(fc_target->predicateLocks),
					 offsetof(PREDICATELOCK, targetLink));
	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
	while (fc_predlock)
	{
		fc_predlocktargetlink = &(fc_predlock->targetLink);
		fc_nextpredlock = (PREDICATELOCK *)
			SHMQueueNext(&(fc_target->predicateLocks),
						 fc_predlocktargetlink,
						 offsetof(PREDICATELOCK, targetLink));

		SHMQueueDelete(&(fc_predlock->xactLink));
		SHMQueueDelete(&(fc_predlock->targetLink));

		hash_search_with_hash_value
			(PredicateLockHash,
			 &fc_predlock->tag,
			 PredicateLockHashCodeFromTargetHashCode(&fc_predlock->tag,
													 fc_targettaghash),
			 HASH_REMOVE, &fc_found);
		Assert(fc_found);

		fc_predlock = fc_nextpredlock;
	}
	LWLockRelease(SerializableXactHashLock);

	/* 如果可能，移除目标本身。 */
	fc_RemoveTargetIfNoLongerUsed(fc_target, fc_targettaghash);
}


/*
 *		转移谓词锁到新目标
 *
 * 移动或复制锁定目标的所有谓词锁，以供索引页面分裂/合并和其他创建或替换锁定目标的操作使用。如果 'removeOld' 为真，则旧锁和目标将被移除。
 *
 * 成功时返回 true，如果我们没有足够的共享内存来分配新目标或锁，则返回 false。 如果设置了 removeOld（通过使用 PredicateLockTargetHash 中的临时条目作为临时空间），则保证始终成功。
 *
 * 警告：“removeOld”选项应谨慎使用，因为此函数不会（实际上，不能）更新其他后端的 LocalPredicateLockHash。如果我们仅添加新条目，这不是问题：本地锁表仅作为提示使用，因此缺失的锁条目是可以的。当使用“removeOld”时，可能会出现已不再持有的锁的条目，这通常是不可以的。我们只能在用覆盖它的更粗粒度锁替换锁时安全使用，或者如果我们绝对确定将来没有人需要引用该锁时。
 *
 * 调用者必须独占持有 SerializablePredicateListLock。
 */
static bool fc_TransferPredicateLocksToNewTarget(PREDICATELOCKTARGETTAG fc_oldtargettag,
								  PREDICATELOCKTARGETTAG fc_newtargettag,
								  bool fc_removeOld)
{
	uint32		fc_oldtargettaghash;
	LWLock	   *fc_oldpartitionLock;
	PREDICATELOCKTARGET *fc_oldtarget;
	uint32		fc_newtargettaghash;
	LWLock	   *fc_newpartitionLock;
	bool		fc_found;
	bool		fc_outOfShmem = false;

	Assert(LWLockHeldByMeInMode(SerializablePredicateListLock,
								LW_EXCLUSIVE));

	fc_oldtargettaghash = PredicateLockTargetTagHashCode(&fc_oldtargettag);
	fc_newtargettaghash = PredicateLockTargetTagHashCode(&fc_newtargettag);
	fc_oldpartitionLock = PredicateLockHashPartitionLock(fc_oldtargettaghash);
	fc_newpartitionLock = PredicateLockHashPartitionLock(fc_newtargettaghash);

	if (fc_removeOld)
	{
		/*
		 * 移除虚拟条目以提供临时空间，以便我们知道我们将能够
		 * 创建新的锁目标。
		 */
		fc_RemoveScratchTarget(false);
	}

	/*
	 * 我们必须按升序获取分区锁以避免死锁。如果旧分区和新分区相同，
	 * 我们只需请求一次锁。
	 */
	if (fc_oldpartitionLock < fc_newpartitionLock)
	{
		LWLockAcquire(fc_oldpartitionLock,
					  (fc_removeOld ? LW_EXCLUSIVE : LW_SHARED));
		LWLockAcquire(fc_newpartitionLock, LW_EXCLUSIVE);
	}
	else if (fc_oldpartitionLock > fc_newpartitionLock)
	{
		LWLockAcquire(fc_newpartitionLock, LW_EXCLUSIVE);
		LWLockAcquire(fc_oldpartitionLock,
					  (fc_removeOld ? LW_EXCLUSIVE : LW_SHARED));
	}
	else
		LWLockAcquire(fc_newpartitionLock, LW_EXCLUSIVE);

	/*
	 * 查找旧目标。如果未找到，没关系；没有影响任何谓词锁，
	 * 所以我们可以清理并返回。如果存在，遍历其谓词锁列表,
	 * 并将它们移动或复制到新目标。
	 */
	fc_oldtarget = hash_search_with_hash_value(PredicateLockTargetHash,
											&fc_oldtargettag,
											fc_oldtargettaghash,
											HASH_FIND, NULL);

	if (fc_oldtarget)
	{
		PREDICATELOCKTARGET *fc_newtarget;
		PREDICATELOCK *fc_oldpredlock;
		PREDICATELOCKTAG fc_newpredlocktag;

		fc_newtarget = hash_search_with_hash_value(PredicateLockTargetHash,
												&fc_newtargettag,
												fc_newtargettaghash,
												HASH_ENTER_NULL, &fc_found);

		if (!fc_newtarget)
		{
			/* 由于共享内存不足而分配失败 */
			fc_outOfShmem = true;
			goto exit;
		}

		/* 如果我们创建了新的条目，初始化它 */
		if (!fc_found)
			SHMQueueInit(&(fc_newtarget->predicateLocks));

		fc_newpredlocktag.myTarget = fc_newtarget;

		/*
		 * 遍历旧目标上的所有锁，用新目标上的锁替换它们。
		 */
		fc_oldpredlock = (PREDICATELOCK *)
			SHMQueueNext(&(fc_oldtarget->predicateLocks),
						 &(fc_oldtarget->predicateLocks),
						 offsetof(PREDICATELOCK, targetLink));
		LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
		while (fc_oldpredlock)
		{
			SHM_QUEUE  *fc_predlocktargetlink;
			PREDICATELOCK *fc_nextpredlock;
			PREDICATELOCK *fc_newpredlock;
			SerCommitSeqNo fc_oldCommitSeqNo = fc_oldpredlock->commitSeqNo;

			fc_predlocktargetlink = &(fc_oldpredlock->targetLink);
			fc_nextpredlock = (PREDICATELOCK *)
				SHMQueueNext(&(fc_oldtarget->predicateLocks),
							 fc_predlocktargetlink,
							 offsetof(PREDICATELOCK, targetLink));
			fc_newpredlocktag.myXact = fc_oldpredlock->tag.myXact;

			if (fc_removeOld)
			{
				SHMQueueDelete(&(fc_oldpredlock->xactLink));
				SHMQueueDelete(&(fc_oldpredlock->targetLink));

				hash_search_with_hash_value
					(PredicateLockHash,
					 &fc_oldpredlock->tag,
					 PredicateLockHashCodeFromTargetHashCode(&fc_oldpredlock->tag,
															 fc_oldtargettaghash),
					 HASH_REMOVE, &fc_found);
				Assert(fc_found);
			}

			fc_newpredlock = (PREDICATELOCK *)
				hash_search_with_hash_value(PredicateLockHash,
											&fc_newpredlocktag,
											PredicateLockHashCodeFromTargetHashCode(&fc_newpredlocktag,
																					fc_newtargettaghash),
											HASH_ENTER_NULL,
											&fc_found);
			if (!fc_newpredlock)
			{
				/* 共享内存不足。撤销我们到目前为止所做的操作。 */
				LWLockRelease(SerializableXactHashLock);
				fc_DeleteLockTarget(fc_newtarget, fc_newtargettaghash);
				fc_outOfShmem = true;
				goto exit;
			}
			if (!fc_found)
			{
				SHMQueueInsertBefore(&(fc_newtarget->predicateLocks),
									 &(fc_newpredlock->targetLink));
				SHMQueueInsertBefore(&(fc_newpredlocktag.myXact->predicateLocks),
									 &(fc_newpredlock->xactLink));
				fc_newpredlock->commitSeqNo = fc_oldCommitSeqNo;
			}
			else
			{
				if (fc_newpredlock->commitSeqNo < fc_oldCommitSeqNo)
					fc_newpredlock->commitSeqNo = fc_oldCommitSeqNo;
			}

			Assert(fc_newpredlock->commitSeqNo != 0);
			Assert((fc_newpredlock->commitSeqNo == InvalidSerCommitSeqNo)
				   || (fc_newpredlock->tag.myXact == OldCommittedSxact));

			fc_oldpredlock = fc_nextpredlock;
		}
		LWLockRelease(SerializableXactHashLock);

		if (fc_removeOld)
		{
			Assert(SHMQueueEmpty(&fc_oldtarget->predicateLocks));
			fc_RemoveTargetIfNoLongerUsed(fc_oldtarget, fc_oldtargettaghash);
		}
	}


exit:
	/* 按照获取的相反顺序释放分区锁。 */
	if (fc_oldpartitionLock < fc_newpartitionLock)
	{
		LWLockRelease(fc_newpartitionLock);
		LWLockRelease(fc_oldpartitionLock);
	}
	else if (fc_oldpartitionLock > fc_newpartitionLock)
	{
		LWLockRelease(fc_oldpartitionLock);
		LWLockRelease(fc_newpartitionLock);
	}
	else
		LWLockRelease(fc_newpartitionLock);

	if (fc_removeOld)
	{
		/* 如果我们在移动锁，应该不会耗尽内存 */
		Assert(!fc_outOfShmem);

		/* 将临时条目放回 */
		fc_RestoreScratchTarget(false);
	}

	return !fc_outOfShmem;
}

/*
 * 从指定关系中删除所有粒度的谓词锁，该关系可以是堆关系或索引关系。
 * 如果'transfer'为真，则为在指定关系上具有任何锁的事务获取
 * 堆上的关系锁。
 *
 * 这需要获取大量的LW锁，并扫描整个锁目标表以寻找匹配项。
 * 这使得其成本高于大多数谓词锁管理函数，但它只会在DDL
 * 类型命令（无论如何代价高昂）的情况下调用，当没有可序列化
 * 事务处于活动状态或关系是临时时会有快速返回。
 *
 * 我们不使用TransferPredicateLocksToNewTarget函数，因为它
 * 在两个目标的分区上获取自己的锁，而我们将已经持有所有分区锁。
 *
 * 我们不能从这里抛出错误，因为调用可能来自于一个不是
 * 可序列化的事务。
 *
 * 注意：目前仅在transfer设置为true时调用，但这可能会更改。
 * 如果我们决定在执行DROP TABLE的事务提交时清理表的锁，
 * 那么false条件将是有用的。
 */
static void fc_DropAllPredicateLocksFromTable(Relation fc_relation, bool fc_transfer)
{
	HASH_SEQ_STATUS fc_seqstat;
	PREDICATELOCKTARGET *fc_oldtarget;
	PREDICATELOCKTARGET *fc_heaptarget;
	Oid			fc_dbId;
	Oid			fc_relId;
	Oid			fc_heapId;
	int			fc_i;
	bool		fc_isIndex;
	bool		fc_found;
	uint32		fc_heaptargettaghash;

	/*
	 * 如果没有可序列化事务正在运行，则快速退出。
	 * 在不获取锁的情况下检查这是安全的，因为调用者持有
	 * 该关系上的ACCESS EXCLUSIVE锁。在持有此锁的情况下，
	 * 不会有新锁对这里重要的锁。
	 */
	if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
		return;

	if (!fc_PredicateLockingNeededForRelation(fc_relation))
		return;

	fc_dbId = fc_relation->rd_node.dbNode;
	fc_relId = fc_relation->rd_id;
	if (fc_relation->rd_index == NULL)
	{
		fc_isIndex = false;
		fc_heapId = fc_relId;
	}
	else
	{
		fc_isIndex = true;
		fc_heapId = fc_relation->rd_index->indrelid;
	}
	Assert(fc_heapId != InvalidOid);
	Assert(fc_transfer || !fc_isIndex);	/* 索引OID仅在转移时才有意义 */

	/* 第一次需要时检索，然后保持。 */
	fc_heaptargettaghash = 0;
	fc_heaptarget = NULL;

	/* 在所有锁分区上获取锁 */
	LWLockAcquire(SerializablePredicateListLock, LW_EXCLUSIVE);
	for (fc_i = 0; fc_i < NUM_PREDICATELOCK_PARTITIONS; fc_i++)
		LWLockAcquire(PredicateLockHashPartitionLockByIndex(fc_i), LW_EXCLUSIVE);
	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

	/*
	 * 移除虚拟条目以提供临时空间，以便我们知道我们将能够
	 * 创建新的锁目标。
	 */
	if (fc_transfer)
		fc_RemoveScratchTarget(true);

	/* 扫描目标映射 */
	hash_seq_init(&fc_seqstat, PredicateLockTargetHash);

	while ((fc_oldtarget = (PREDICATELOCKTARGET *) hash_seq_search(&fc_seqstat)))
	{
		PREDICATELOCK *fc_oldpredlock;

		/*
		 * 检查这是否是一个需要注意的目标。
		 */
		if (GET_PREDICATELOCKTARGETTAG_RELATION(fc_oldtarget->tag) != fc_relId)
			continue;			/* 错误的关系ID */
		if (GET_PREDICATELOCKTARGETTAG_DB(fc_oldtarget->tag) != fc_dbId)
			continue;			/* 错误的数据库ID */
		if (fc_transfer && !fc_isIndex
			&& GET_PREDICATELOCKTARGETTAG_TYPE(fc_oldtarget->tag) == PREDLOCKTAG_RELATION)
			continue;			/* 已经是正确的锁 */

		/*
		 * 如果我们到了这里，我们有工作要做。我们确保堆
		 * 关系锁存在，然后遍历我们找到的旧目标的谓词锁列表，
		 * 将所有锁移动到堆关系锁上——除非它们已经持有该锁。
		 */

		/*
		 * 首先确保我们有堆关系目标。我们只需
		 * 做这一次。
		 */
		if (fc_transfer && fc_heaptarget == NULL)
		{
			PREDICATELOCKTARGETTAG fc_heaptargettag;

			SET_PREDICATELOCKTARGETTAG_RELATION(fc_heaptargettag, fc_dbId, fc_heapId);
			fc_heaptargettaghash = PredicateLockTargetTagHashCode(&fc_heaptargettag);
			fc_heaptarget = hash_search_with_hash_value(PredicateLockTargetHash,
													 &fc_heaptargettag,
													 fc_heaptargettaghash,
													 HASH_ENTER, &fc_found);
			if (!fc_found)
				SHMQueueInit(&fc_heaptarget->predicateLocks);
		}

		/*
		 * 遍历旧目标上的所有锁，用新目标上的锁替换它们。
		 */
		fc_oldpredlock = (PREDICATELOCK *)
			SHMQueueNext(&(fc_oldtarget->predicateLocks),
						 &(fc_oldtarget->predicateLocks),
						 offsetof(PREDICATELOCK, targetLink));
		while (fc_oldpredlock)
		{
			PREDICATELOCK *fc_nextpredlock;
			PREDICATELOCK *fc_newpredlock;
			SerCommitSeqNo fc_oldCommitSeqNo;
			SERIALIZABLEXACT *fc_oldXact;

			fc_nextpredlock = (PREDICATELOCK *)
				SHMQueueNext(&(fc_oldtarget->predicateLocks),
							 &(fc_oldpredlock->targetLink),
							 offsetof(PREDICATELOCK, targetLink));

			/*
			 * 首先移除旧锁。这避免了哈希表锁结构条目耗尽的机会。
			 */
			fc_oldCommitSeqNo = fc_oldpredlock->commitSeqNo;
			fc_oldXact = fc_oldpredlock->tag.myXact;

			SHMQueueDelete(&(fc_oldpredlock->xactLink));

			/*
			 * 不需要从旧目标列表中逐个删除，我们反正要移除
			 * 整个目标。
			 */
			hash_search(PredicateLockHash,
						&fc_oldpredlock->tag,
						HASH_REMOVE, &fc_found);
			Assert(fc_found);

			if (fc_transfer)
			{
				PREDICATELOCKTAG fc_newpredlocktag;

				fc_newpredlocktag.myTarget = fc_heaptarget;
				fc_newpredlocktag.myXact = fc_oldXact;
				fc_newpredlock = (PREDICATELOCK *)
					hash_search_with_hash_value(PredicateLockHash,
												&fc_newpredlocktag,
												PredicateLockHashCodeFromTargetHashCode(&fc_newpredlocktag,
																						fc_heaptargettaghash),
												HASH_ENTER,
												&fc_found);
				if (!fc_found)
				{
					SHMQueueInsertBefore(&(fc_heaptarget->predicateLocks),
										 &(fc_newpredlock->targetLink));
					SHMQueueInsertBefore(&(fc_newpredlocktag.myXact->predicateLocks),
										 &(fc_newpredlock->xactLink));
					fc_newpredlock->commitSeqNo = fc_oldCommitSeqNo;
				}
				else
				{
					if (fc_newpredlock->commitSeqNo < fc_oldCommitSeqNo)
						fc_newpredlock->commitSeqNo = fc_oldCommitSeqNo;
				}

				Assert(fc_newpredlock->commitSeqNo != 0);
				Assert((fc_newpredlock->commitSeqNo == InvalidSerCommitSeqNo)
					   || (fc_newpredlock->tag.myXact == OldCommittedSxact));
			}

			fc_oldpredlock = fc_nextpredlock;
		}

		hash_search(PredicateLockTargetHash, &fc_oldtarget->tag, HASH_REMOVE,
					&fc_found);
		Assert(fc_found);
	}

	/* 将临时条目放回 */
	if (fc_transfer)
		fc_RestoreScratchTarget(true);

	/* 以相反的顺序释放锁 */
	LWLockRelease(SerializableXactHashLock);
	for (fc_i = NUM_PREDICATELOCK_PARTITIONS - 1; fc_i >= 0; fc_i--)
		LWLockRelease(PredicateLockHashPartitionLockByIndex(fc_i));
	LWLockRelease(SerializablePredicateListLock);
}

/*
 * TransferPredicateLocksToHeapRelation
 *		对于所有事务，将给定关系的所有谓词锁转移到堆上的
 *		单个关系锁。
 */
void TransferPredicateLocksToHeapRelation(Relation fc_relation)
{
	fc_DropAllPredicateLocksFromTable(fc_relation, true);
}


/*
 *		PredicateLockPageSplit
 *
 * 将旧页面的任何谓词锁复制到新页面。
 * 如果这是一个临时表或Toast表，则跳过。
 *
 * 注意：页面拆分（或溢出）会影响所有可序列化事务，
 * 即使它发生在其他事务隔离级别的上下文中。
 *
 * 注意：这目前会使局部复制的锁没有关于在共享内存中的新锁的信息。
 * 如果在被锁定页面上发生足够的页面拆分，而持有锁的进程
 * 没有进入并注意到，这可能会导致问题。
 */
void PredicateLockPageSplit(Relation fc_relation, BlockNumber fc_oldblkno,
					   BlockNumber fc_newblkno)
{
	PREDICATELOCKTARGETTAG fc_oldtargettag;
	PREDICATELOCKTARGETTAG fc_newtargettag;
	bool		fc_success;

	
/*
	 * 如果没有正在运行的可串行化事务，则快速退出。
	 *
	 * 在不采取任何额外锁的情况下进行此检查是安全的。即使
	 * 一个可串行化事务同时开始，我们也知道它不能在被分割的页面上获取
	 * 任何 SIREAD 锁，因为调用者持有
	 * 相关的缓冲页锁。内存重排序不是问题；在 LWLock 获取中的内存屏障保证了在持有缓冲页锁时发生此读取。
	 */
	if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
		return;

	if (!fc_PredicateLockingNeededForRelation(fc_relation))
		return;

	Assert(fc_oldblkno != fc_newblkno);
	Assert(BlockNumberIsValid(fc_oldblkno));
	Assert(BlockNumberIsValid(fc_newblkno));

	SET_PREDICATELOCKTARGETTAG_PAGE(fc_oldtargettag,
									fc_relation->rd_node.dbNode,
									fc_relation->rd_id,
									fc_oldblkno);
	SET_PREDICATELOCKTARGETTAG_PAGE(fc_newtargettag,
									fc_relation->rd_node.dbNode,
									fc_relation->rd_id,
									fc_newblkno);

	LWLockAcquire(SerializablePredicateListLock, LW_EXCLUSIVE);

	/*
	 * 尝试将锁复制到新页面的标签上，如有必要则创建它。
	 */
	fc_success = fc_TransferPredicateLocksToNewTarget(fc_oldtargettag,
												fc_newtargettag,
												false);

	if (!fc_success)
	{
		/*
		 * 没有更多的谓词锁条目可用。这里失败不是一个选项，因此将页面锁提升为关系锁。
		 */

		/* 获取父关系锁的锁标记 */
		fc_success = fc_GetParentPredicateLockTag(&fc_oldtargettag,
											&fc_newtargettag);
		Assert(fc_success);

		/*
		 * 将锁移动到父级。这不应该失败。
		 *
		 * 请注意，这里我们正在移除其他后端持有的锁，
		 * 可能导致它们的本地锁散列表中的不一致。
		 * 这是可以的，因为我们将其替换为覆盖旧锁的锁。
		 */
		fc_success = fc_TransferPredicateLocksToNewTarget(fc_oldtargettag,
													fc_newtargettag,
													true);
		Assert(fc_success);
	}

	LWLockRelease(SerializablePredicateListLock);
}

/*
 *		PredicateLockPageCombine
 *
 * 合并两个现有页面的谓词锁。
 * 如果这是一个临时表或 toast 表，则跳过。
 *
 * 注意：页面合并会影响所有可序列化事务，即使它
 * 发生在其他事务隔离级别的上下文中。
 */
void PredicateLockPageCombine(Relation fc_relation, BlockNumber fc_oldblkno,
						 BlockNumber fc_newblkno)
{
	/*
	 * 页面合并与页面拆分的区别在于，在将锁转移到新页面之后，
	 * 我们应该能够移除旧页面上的锁，而不是复制它们。然而，因为我们无法编辑其他
	 * 后端的本地锁表，移除旧锁将使它们在其 LocalPredicateLockHash 中留下一个
	 * 它们没有持有的锁条目，这是不可接受的。所以我们最终不得不做与页面拆分
	 * 相同的工作，在新页面上获取锁，并保持旧页面锁定。这可能会导致一些误报，但在实践中应该是稀少的。
	 */
	PredicateLockPageSplit(fc_relation, fc_oldblkno, fc_newblkno);
}

/*
 * 遍历正在进行的可序列化事务列表并找到新的
 * xmin。
 */
static void fc_SetNewSxactGlobalXmin(void)
{
	SERIALIZABLEXACT *fc_sxact;

	Assert(LWLockHeldByMe(SerializableXactHashLock));

	PredXact->SxactGlobalXmin = InvalidTransactionId;
	PredXact->SxactGlobalXminCount = 0;

	for (fc_sxact = fc_FirstPredXact(); fc_sxact != NULL; fc_sxact = fc_NextPredXact(fc_sxact))
	{
		if (!SxactIsRolledBack(fc_sxact)
			&& !SxactIsCommitted(fc_sxact)
			&& fc_sxact != OldCommittedSxact)
		{
			Assert(fc_sxact->xmin != InvalidTransactionId);
			if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)
				|| TransactionIdPrecedes(fc_sxact->xmin,
										 PredXact->SxactGlobalXmin))
			{
				PredXact->SxactGlobalXmin = fc_sxact->xmin;
				PredXact->SxactGlobalXminCount = 1;
			}
			else if (TransactionIdEquals(fc_sxact->xmin,
										 PredXact->SxactGlobalXmin))
				PredXact->SxactGlobalXminCount++;
		}
	}

	fc_SerialSetActiveSerXmin(PredXact->SxactGlobalXmin);
}

/*
 *		ReleasePredicateLocks
 *
 * 根据当前事务的完成情况释放谓词锁，
 * 无论是提交还是回滚。它也可以在只读
 * 事务中调用，当该事务变得不可能成为
 * 危险结构的一部分时。
 *
 * 我们什么都不做，除非这是一个可序列化事务。
 *
 * 此方法必须确保共享内存散列表在某种相对及时的方式中被清理。
 *
 * 如果此事务正在提交并持有任何谓词锁，
 * 它必须被添加到仍持有锁的已完成可序列化事务列表中。
 *
 * 如果 isReadOnlySafe 为真，则在事务结束之前释放谓词锁，因为 MySerializableXact 已被确定为 RO_SAFE。
 * 在非并行模式中我们可以完全释放它，但在并行模式中我们部分释放 SERIALIZABLEXACT 并将其保留到事务结束，允许每个后端在自己的时间内清除其 MySerializableXact 变量并从优化中受益。
 */
void ReleasePredicateLocks(bool fc_isCommit, bool fc_isReadOnlySafe)
{
	bool		fc_partiallyReleasing = false;
	bool		fc_needToClear;
	RWConflict	fc_conflict,
				fc_nextConflict,
				fc_possibleUnsafeConflict;
	SERIALIZABLEXACT *fc_roXact;

	/*
	 * 我们不能信任 XactReadOnly，因为一个开始
	 * 为读写的事务可以在后续显示为只读，例如，在
	 * 子事务中。如果它在没有写入的情况下提交，我们希望将事务标记为只读，
	 * 以便实际上只读的事务能够获得一些只读优化的好处，因此我们将使用这个局部变量
	 * 以确保与事务是否在顶层声明为只读相关的清理逻辑正确。
	 */
	bool		fc_topLevelIsDeclaredReadOnly;

	/* 由于 RO_SAFE，我们不能同时进行提交和提前释放。 */
	Assert(!(fc_isCommit && fc_isReadOnlySafe));

	/* 我们是在事务的结束，即提交或中止处吗？ */
	if (!fc_isReadOnlySafe)
	{
		/*
		 * 并行工作者在其事务结束时不能释放谓词锁。
		 * 领导者将在其事务结束时执行此操作。
		 */
		if (IsParallelWorker())
		{
			fc_ReleasePredicateLocksLocal();
			return;
		}

		/*
		 * 当并行查询中的领导者到达事务结束时，
		 * 它已等待所有工作者退出。
		 */
		Assert(!ParallelContextActive());

		/*
		 * 如果并行查询中的领导者之前暂存了部分
		 * 释放的 SERIALIZABLEXACT 以便在事务结束时进行最终清理 
		 * （因为工作者可能仍在访问它），那么现在是恢复它的时候了。
		 */
		if (SavedSerializableXact != InvalidSerializableXact)
		{
			Assert(MySerializableXact == InvalidSerializableXact);
			MySerializableXact = SavedSerializableXact;
			SavedSerializableXact = InvalidSerializableXact;
			Assert(SxactIsPartiallyReleased(MySerializableXact));
		}
	}

	if (MySerializableXact == InvalidSerializableXact)
	{
		Assert(LocalPredicateLockHash == NULL);
		return;
	}

	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

	/*
	 * 如果事务正在提交，但它已经被部分释放，
	 * 则将其视为回滚。它标记为已回滚。
	 */
	if (fc_isCommit && SxactIsPartiallyReleased(MySerializableXact))
		fc_isCommit = false;

	/*
	 * 如果我们在事务中间被调用，因为我们发现
	 * SXACT_FLAG_RO_SAFE 标志被设置，则如果我们是第一个注意到者，
	 * 我们将部分释放它（即，释放谓词锁和冲突，但不释放
	 * SERIALIZABLEXACT 本身）。
	 */
	if (fc_isReadOnlySafe && IsInParallelMode())
	{
		/*
		 * 领导者需要存储一个指针，以便它可以
		 * 在事务结束时完全释放它。
		 */
		if (!IsParallelWorker())
			SavedSerializableXact = MySerializableXact;

		/*
		 * 第一个达到此条件的后端将部分释放
		 * SERIALIZABLEXACT。其他所有后端只会清除它们的
		 * 后端本地状态，以便在事务的其余时间停止执行 SSI 检查。
		 */
		if (SxactIsPartiallyReleased(MySerializableXact))
		{
			LWLockRelease(SerializableXactHashLock);
			fc_ReleasePredicateLocksLocal();
			return;
		}
		else
		{
			MySerializableXact->flags |= SXACT_FLAG_PARTIALLY_RELEASED;
			fc_partiallyReleasing = true;
			/* ... 然后继续进行下面的部分释放。 */
		}
	}
	Assert(!fc_isCommit || SxactIsPrepared(MySerializableXact));
	Assert(!fc_isCommit || !SxactIsDoomed(MySerializableXact));
	Assert(!SxactIsCommitted(MySerializableXact));
	Assert(SxactIsPartiallyReleased(MySerializableXact)
		   || !SxactIsRolledBack(MySerializableXact));

	/* 在准备提交/回滚期间可能不具有可序列化性 */
	Assert(MySerializableXact->pid == 0 || IsolationIsSerializable());

	/* 我们最好不要已经在清理列表中。 */
	Assert(!SxactIsOnFinishedList(MySerializableXact));

	fc_topLevelIsDeclaredReadOnly = SxactIsReadOnly(MySerializableXact);

	
/*
	 * 我们在这里不持有 XidGenLock 锁，假设 TransactionId 是
	 * 原子的！
	 *
	 * 如果这个值在变化，我们不太在乎我们得到的是旧值还是新值——它只是用来确定
	 * SxactGlobalXmin 必须推进到何种程度，才能完全清理这个事务。最坏的情况是我们需要等到另一个
	 * 事务完成之后才能释放一些 RAM；可见行为的正确性不受影响。
	 */
	MySerializableXact->finishedBefore = XidFromFullTransactionId(ShmemVariableCache->nextXid);

	/*
	 * 如果不是提交，那就是回滚或被标记为 SXACT_FLAG_RO_SAFE 的只读事务，
	 * 我们可以立即清除我们的锁。
	 */
	if (fc_isCommit)
	{
		MySerializableXact->flags |= SXACT_FLAG_COMMITTED;
		MySerializableXact->commitSeqNo = ++(PredXact->LastSxactCommitSeqNo);
		/* 识别隐式只读事务（没有写的提交）。 */
		if (!MyXactDidWrite)
			MySerializableXact->flags |= SXACT_FLAG_READ_ONLY;
	}
	else
	{
		/*
		 * DOOMED 标志表示我们打算回滚这个
		 * 事务，因此它不应导致与其他冲突事务的序列化失败。请注意，如果另一个后端将这个事务标记为
		 * 回滚，该标志可能已经被设置。
		 *
		 * ROLLED_BACK 标志进一步表示已经调用了 ReleasePredicateLocks，
		 * 因此 SerializableXact 有资格进行清理。这意味着在计算
		 * SxactGlobalXmin 时不应考虑它。
		 */
		MySerializableXact->flags |= SXACT_FLAG_DOOMED;
		MySerializableXact->flags |= SXACT_FLAG_ROLLED_BACK;

		/*
		 * 如果该事务之前已准备好，但现在因 ROLLBACK PREPARED 或
		 * （希望非常罕见的）准备后错误而失败，清除准备标志。这简化了冲突
		 * 检查。
		 */
		MySerializableXact->flags &= ~SXACT_FLAG_PREPARED;
	}

	if (!fc_topLevelIsDeclaredReadOnly)
	{
		Assert(PredXact->WritableSxactCount > 0);
		if (--(PredXact->WritableSxactCount) == 0)
		{
			/*
			 * 释放所有已提交事务的谓词锁和读写冲突。 现在没有任何事务可能
			 * 与这些锁发生冲突，也没有新事务重叠的机会。 同样，现有的冲突不能
			 * 引起重定位，任何可能完成危险结构的冲突都已经导致了回滚，因此任何
			 * 剩余的冲突必须是无害的。
			 */
			PredXact->CanPartialClearThrough = PredXact->LastSxactCommitSeqNo;
		}
	}
	else
	{
		/*
		 * 只读事务：清除可能使我们不安全的事务列表。 注意，我们使用 'inLink' 进行
		 * 迭代，而不是读/写事务的 'outLink'。
		 */
		fc_possibleUnsafeConflict = (RWConflict)
			SHMQueueNext(&MySerializableXact->possibleUnsafeConflicts,
						 &MySerializableXact->possibleUnsafeConflicts,
						 offsetof(RWConflictData, inLink));
		while (fc_possibleUnsafeConflict)
		{
			fc_nextConflict = (RWConflict)
				SHMQueueNext(&MySerializableXact->possibleUnsafeConflicts,
							 &fc_possibleUnsafeConflict->inLink,
							 offsetof(RWConflictData, inLink));

			Assert(!SxactIsReadOnly(fc_possibleUnsafeConflict->sxactOut));
			Assert(MySerializableXact == fc_possibleUnsafeConflict->sxactIn);

			fc_ReleaseRWConflict(fc_possibleUnsafeConflict);

			fc_possibleUnsafeConflict = fc_nextConflict;
		}
	}

	/* 检查与旧提交事务的冲突。 */
	if (fc_isCommit
		&& !SxactIsReadOnly(MySerializableXact)
		&& SxactHasSummaryConflictOut(MySerializableXact))
	{
		/*
		 * 我们不知道与哪个旧提交事务发生了冲突，
		 * 所以要保守些，这里使用 FirstNormalSerCommitSeqNo。
		 */
		MySerializableXact->SeqNo.earliestOutConflictCommit =
			FirstNormalSerCommitSeqNo;
		MySerializableXact->flags |= SXACT_FLAG_CONFLICT_OUT;
	}

	/*
	 * 释放所有与已提交事务的外部冲突。如果我们正在回滚，则将它们全部清除。
	 * 如果任何指向以前的已提交事务，则设置 SXACT_FLAG_CONFLICT_OUT。
	 */
	fc_conflict = (RWConflict)
		SHMQueueNext(&MySerializableXact->outConflicts,
					 &MySerializableXact->outConflicts,
					 offsetof(RWConflictData, outLink));
	while (fc_conflict)
	{
		fc_nextConflict = (RWConflict)
			SHMQueueNext(&MySerializableXact->outConflicts,
						 &fc_conflict->outLink,
						 offsetof(RWConflictData, outLink));

		if (fc_isCommit
			&& !SxactIsReadOnly(MySerializableXact)
			&& SxactIsCommitted(fc_conflict->sxactIn))
		{
			if ((MySerializableXact->flags & SXACT_FLAG_CONFLICT_OUT) == 0
				|| fc_conflict->sxactIn->prepareSeqNo < MySerializableXact->SeqNo.earliestOutConflictCommit)
				MySerializableXact->SeqNo.earliestOutConflictCommit = fc_conflict->sxactIn->prepareSeqNo;
			MySerializableXact->flags |= SXACT_FLAG_CONFLICT_OUT;
		}

		if (!fc_isCommit
			|| SxactIsCommitted(fc_conflict->sxactIn)
			|| (fc_conflict->sxactIn->SeqNo.lastCommitBeforeSnapshot >= PredXact->LastSxactCommitSeqNo))
			fc_ReleaseRWConflict(fc_conflict);

		fc_conflict = fc_nextConflict;
	}

	/*
	 * 释放所有来自已提交和只读事务的内部冲突。如果我们正在回滚，则将它们全部清除。
	 */
	fc_conflict = (RWConflict)
		SHMQueueNext(&MySerializableXact->inConflicts,
					 &MySerializableXact->inConflicts,
					 offsetof(RWConflictData, inLink));
	while (fc_conflict)
	{
		fc_nextConflict = (RWConflict)
			SHMQueueNext(&MySerializableXact->inConflicts,
						 &fc_conflict->inLink,
						 offsetof(RWConflictData, inLink));

		if (!fc_isCommit
			|| SxactIsCommitted(fc_conflict->sxactOut)
			|| SxactIsReadOnly(fc_conflict->sxactOut))
			fc_ReleaseRWConflict(fc_conflict);

		fc_conflict = fc_nextConflict;
	}

	if (!fc_topLevelIsDeclaredReadOnly)
	{
		/*
		 * 将我们自己从可能的并发
		 * 只读事务的冲突列表中移除，如果我们有输出冲突，将它们标记为不安全。
		 * 如果有任何等待的可延迟事务，如果它们已知安全或已知不安全，则唤醒它们。
		 */
		fc_possibleUnsafeConflict = (RWConflict)
			SHMQueueNext(&MySerializableXact->possibleUnsafeConflicts,
						 &MySerializableXact->possibleUnsafeConflicts,
						 offsetof(RWConflictData, outLink));
		while (fc_possibleUnsafeConflict)
		{
			fc_nextConflict = (RWConflict)
				SHMQueueNext(&MySerializableXact->possibleUnsafeConflicts,
							 &fc_possibleUnsafeConflict->outLink,
							 offsetof(RWConflictData, outLink));

			fc_roXact = fc_possibleUnsafeConflict->sxactIn;
			Assert(MySerializableXact == fc_possibleUnsafeConflict->sxactOut);
			Assert(SxactIsReadOnly(fc_roXact));

			/* 如有必要，标记为冲突。 */
			if (fc_isCommit
				&& MyXactDidWrite
				&& SxactHasConflictOut(MySerializableXact)
				&& (MySerializableXact->SeqNo.earliestOutConflictCommit
					<= fc_roXact->SeqNo.lastCommitBeforeSnapshot))
			{
				/*
				 * 这释放可能的可能不安全冲突（以及所有其他
				 * roXact的可能冲突）
				 */
				fc_FlagSxactUnsafe(fc_roXact);
			}
			else
			{
				fc_ReleaseRWConflict(fc_possibleUnsafeConflict);

				/*
				 * 如果我们是最后一个可能的冲突，将其标记为安全。
				 * 该事务现在可以安全地释放其谓词锁（但是该事务的后端必须自己执行该操作）。
				 */
				if (SHMQueueEmpty(&fc_roXact->possibleUnsafeConflicts))
					fc_roXact->flags |= SXACT_FLAG_RO_SAFE;
			}

			/*
			 * 如果我们现在知道它是安全的或有冲突的，唤醒等待的可延迟事务的进程。
			 */
			if (SxactIsDeferrableWaiting(fc_roXact) &&
				(SxactIsROUnsafe(fc_roXact) || SxactIsROSafe(fc_roXact)))
				ProcSendSignal(fc_roXact->pgprocno);

			fc_possibleUnsafeConflict = fc_nextConflict;
		}
	}

	/*
	 * 检查是否是清理旧事务的时候。只有在
	 * 具有最旧xmin的最后一个可串行化事务完成时，
	 * 才能进行此操作。然后我们找到“新最旧”
	 * xmin，并清除在该事务启动之前完成的任何事务。
	 *
	 * 对于只读事务中的并行查询，它可能会运行两次。
	 * 我们只在第一次调用时释放引用。
	 */
	fc_needToClear = false;
	if ((fc_partiallyReleasing ||
		 !SxactIsPartiallyReleased(MySerializableXact)) &&
		TransactionIdEquals(MySerializableXact->xmin,
							PredXact->SxactGlobalXmin))
	{
		Assert(PredXact->SxactGlobalXminCount > 0);
		if (--(PredXact->SxactGlobalXminCount) == 0)
		{
			fc_SetNewSxactGlobalXmin();
			fc_needToClear = true;
		}
	}

	LWLockRelease(SerializableXactHashLock);

	LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE);

	/* 将此添加到稍后清理的事务列表中。 */
	if (fc_isCommit)
		SHMQueueInsertBefore(FinishedSerializableTransactions,
							 &MySerializableXact->finishedLink);

	/*
	 * 如果我们在并行模式下释放RO_SAFE事务，我们将只
	 * 部分释放它。这是必要的，因为其他后端可能对此有
	 * 引用。领导者将在所有工作线程停止运行后
	 * 在事务结束时释放SERIALIZABLEXACT。
	 */
	if (!fc_isCommit)
		fc_ReleaseOneSerializableXact(MySerializableXact,
								   fc_isReadOnlySafe && IsInParallelMode(),
								   false);

	LWLockRelease(SerializableFinishedListLock);

	if (fc_needToClear)
		fc_ClearOldPredicateLocks();

	fc_ReleasePredicateLocksLocal();
}

static void fc_ReleasePredicateLocksLocal(void)
{
	MySerializableXact = InvalidSerializableXact;
	MyXactDidWrite = false;

	/* 删除每个事务的锁表 */
	if (LocalPredicateLockHash != NULL)
	{
		hash_destroy(LocalPredicateLockHash);
		LocalPredicateLockHash = NULL;
	}
}

/*
 * 清除旧的谓词锁，这些锁属于已提交的事务，
 * 这些事务对任何正在进行的事务不再有趣。
 */
static void fc_ClearOldPredicateLocks(void)
{
	SERIALIZABLEXACT *fc_finishedSxact;
	PREDICATELOCK *fc_predlock;

	/*
	 * 遍历已完成的事务。它们按提交顺序排列，因此我们可以
	 * 在找到一个仍然有趣的事务时停止。
	 */
	LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE);
	fc_finishedSxact = (SERIALIZABLEXACT *)
		SHMQueueNext(FinishedSerializableTransactions,
					 FinishedSerializableTransactions,
					 offsetof(SERIALIZABLEXACT, finishedLink));
	LWLockAcquire(SerializableXactHashLock, LW_SHARED);
	while (fc_finishedSxact)
	{
		SERIALIZABLEXACT *fc_nextSxact;

		fc_nextSxact = (SERIALIZABLEXACT *)
			SHMQueueNext(FinishedSerializableTransactions,
						 &(fc_finishedSxact->finishedLink),
						 offsetof(SERIALIZABLEXACT, finishedLink));
		if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)
			|| TransactionIdPrecedesOrEquals(fc_finishedSxact->finishedBefore,
											 PredXact->SxactGlobalXmin))
		{
			/*
			 * 该事务在任何正在进行的事务
			 * 拍摄快照之前提交。它不再有趣。
			 */
			LWLockRelease(SerializableXactHashLock);
			SHMQueueDelete(&(fc_finishedSxact->finishedLink));
			fc_ReleaseOneSerializableXact(fc_finishedSxact, false, false);
			LWLockAcquire(SerializableXactHashLock, LW_SHARED);
		}
		else if (fc_finishedSxact->commitSeqNo > PredXact->HavePartialClearedThrough
				 && fc_finishedSxact->commitSeqNo <= PredXact->CanPartialClearThrough)
		{
			/*
			 * 在该事务提交之前拍摄快照的任何活动事务都是只读的，
			 * 因此我们可以清除其状态的一部分。
			 */
			LWLockRelease(SerializableXactHashLock);

			if (SxactIsReadOnly(fc_finishedSxact))
			{
				/* 只读事务可以完全移除 */
				SHMQueueDelete(&(fc_finishedSxact->finishedLink));
				fc_ReleaseOneSerializableXact(fc_finishedSxact, false, false);
			}
			else
			{
				/*
				 * 读写事务只能部分清除。
				 * 我们需要保留SERIALIZABLEXACT，但可以释放
				 * SIREAD锁和冲突。
				 */
				fc_ReleaseOneSerializableXact(fc_finishedSxact, true, false);
			}

			PredXact->HavePartialClearedThrough = fc_finishedSxact->commitSeqNo;
			LWLockAcquire(SerializableXactHashLock, LW_SHARED);
		}
		else
		{
			/* 仍然有趣。 */
			break;
		}
		fc_finishedSxact = fc_nextSxact;
	}
	LWLockRelease(SerializableXactHashLock);

	/*
	 * 遍历虚拟事务上的谓词锁以获取汇总数据。
	 */
	LWLockAcquire(SerializablePredicateListLock, LW_SHARED);
	fc_predlock = (PREDICATELOCK *)
		SHMQueueNext(&OldCommittedSxact->predicateLocks,
					 &OldCommittedSxact->predicateLocks,
					 offsetof(PREDICATELOCK, xactLink));
	while (fc_predlock)
	{
		PREDICATELOCK *fc_nextpredlock;
		bool		fc_canDoPartialCleanup;

		fc_nextpredlock = (PREDICATELOCK *)
			SHMQueueNext(&OldCommittedSxact->predicateLocks,
						 &fc_predlock->xactLink,
						 offsetof(PREDICATELOCK, xactLink));

		LWLockAcquire(SerializableXactHashLock, LW_SHARED);
		Assert(fc_predlock->commitSeqNo != 0);
		Assert(fc_predlock->commitSeqNo != InvalidSerCommitSeqNo);
		fc_canDoPartialCleanup = (fc_predlock->commitSeqNo <= PredXact->CanPartialClearThrough);
		LWLockRelease(SerializableXactHashLock);

		/*
		 * 如果此锁最初属于一个足够旧的事务，我们
		 * 可以释放它。
		 */
		if (fc_canDoPartialCleanup)
		{
			PREDICATELOCKTAG fc_tag;
			PREDICATELOCKTARGET *fc_target;
			PREDICATELOCKTARGETTAG fc_targettag;
			uint32		fc_targettaghash;
			LWLock	   *fc_partitionLock;

			fc_tag = fc_predlock->tag;
			fc_target = fc_tag.myTarget;
			fc_targettag = fc_target->tag;
			fc_targettaghash = PredicateLockTargetTagHashCode(&fc_targettag);
			fc_partitionLock = PredicateLockHashPartitionLock(fc_targettaghash);

			LWLockAcquire(fc_partitionLock, LW_EXCLUSIVE);

			SHMQueueDelete(&(fc_predlock->targetLink));
			SHMQueueDelete(&(fc_predlock->xactLink));

			hash_search_with_hash_value(PredicateLockHash, &fc_tag,
										PredicateLockHashCodeFromTargetHashCode(&fc_tag,
																				fc_targettaghash),
										HASH_REMOVE, NULL);
			fc_RemoveTargetIfNoLongerUsed(fc_target, fc_targettaghash);

			LWLockRelease(fc_partitionLock);
		}

		fc_predlock = fc_nextpredlock;
	}

	LWLockRelease(SerializablePredicateListLock);
	LWLockRelease(SerializableFinishedListLock);
}

/*
 * 这是从任何谓词
 * 锁定哈希表中删除任何内容的正常方法。给定一个我们知道可以被删除的事务：
 * 删除该事务持有的所有谓词锁以及现在没有锁引用的任何谓词锁目标；删除该事务的所有冲突；删除该事务的所有xid值；然后
 * 删除该事务。
 *
 * 当部分标志被设置时，我们可以释放所有谓词锁和
 * 冲突信息——我们已确认不再存在
 * 重叠的读写事务，此事务可能与之相关——但保留事务条目本身和任何outConflicts。
 *
 * 当汇总标志被设置时，我们的sxact数据空间不足
 *，必须汇总到SLRU。谓词锁被转移到一个
 * 虚拟的“旧”事务，对于同一目标的重复锁
 * 会合并为一个带有“最新” commitSeqNo 的锁，从冲突锁中选出..
 */
static void fc_ReleaseOneSerializableXact(SERIALIZABLEXACT *fc_sxact, bool fc_partial,
						   bool fc_summarize)
{
	PREDICATELOCK *fc_predlock;
	SERIALIZABLEXIDTAG fc_sxidtag;
	RWConflict	fc_conflict,
				fc_nextConflict;

	Assert(fc_sxact != NULL);
	Assert(SxactIsRolledBack(fc_sxact) || SxactIsCommitted(fc_sxact));
	Assert(fc_partial || !SxactIsOnFinishedList(fc_sxact));
	Assert(LWLockHeldByMe(SerializableFinishedListLock));

	/*
	 * 首先释放此 xact 持有的所有谓词锁（如果 summarize 为真，则将它们转移到 OldCommittedSxact）
	 */
	LWLockAcquire(SerializablePredicateListLock, LW_SHARED);
	if (IsInParallelMode())
		LWLockAcquire(&fc_sxact->perXactPredicateListLock, LW_EXCLUSIVE);
	fc_predlock = (PREDICATELOCK *)
		SHMQueueNext(&(fc_sxact->predicateLocks),
					 &(fc_sxact->predicateLocks),
					 offsetof(PREDICATELOCK, xactLink));
	while (fc_predlock)
	{
		PREDICATELOCK *fc_nextpredlock;
		PREDICATELOCKTAG fc_tag;
		SHM_QUEUE  *fc_targetLink;
		PREDICATELOCKTARGET *fc_target;
		PREDICATELOCKTARGETTAG fc_targettag;
		uint32		fc_targettaghash;
		LWLock	   *fc_partitionLock;

		fc_nextpredlock = (PREDICATELOCK *)
			SHMQueueNext(&(fc_sxact->predicateLocks),
						 &(fc_predlock->xactLink),
						 offsetof(PREDICATELOCK, xactLink));

		fc_tag = fc_predlock->tag;
		fc_targetLink = &(fc_predlock->targetLink);
		fc_target = fc_tag.myTarget;
		fc_targettag = fc_target->tag;
		fc_targettaghash = PredicateLockTargetTagHashCode(&fc_targettag);
		fc_partitionLock = PredicateLockHashPartitionLock(fc_targettaghash);

		LWLockAcquire(fc_partitionLock, LW_EXCLUSIVE);

		SHMQueueDelete(fc_targetLink);

		hash_search_with_hash_value(PredicateLockHash, &fc_tag,
									PredicateLockHashCodeFromTargetHashCode(&fc_tag,
																			fc_targettaghash),
									HASH_REMOVE, NULL);
		if (fc_summarize)
		{
			bool		fc_found;

			/* 并入虚拟事务列表。 */
			fc_tag.myXact = OldCommittedSxact;
			fc_predlock = hash_search_with_hash_value(PredicateLockHash, &fc_tag,
												   PredicateLockHashCodeFromTargetHashCode(&fc_tag,
																						   fc_targettaghash),
												   HASH_ENTER_NULL, &fc_found);
			if (!fc_predlock)
				ereport(ERROR,
						(errcode(ERRCODE_OUT_OF_MEMORY),
						 errmsg("out of shared memory"),
						 errhint("You might need to increase max_pred_locks_per_transaction.")));
			if (fc_found)
			{
				Assert(fc_predlock->commitSeqNo != 0);
				Assert(fc_predlock->commitSeqNo != InvalidSerCommitSeqNo);
				if (fc_predlock->commitSeqNo < fc_sxact->commitSeqNo)
					fc_predlock->commitSeqNo = fc_sxact->commitSeqNo;
			}
			else
			{
				SHMQueueInsertBefore(&(fc_target->predicateLocks),
									 &(fc_predlock->targetLink));
				SHMQueueInsertBefore(&(OldCommittedSxact->predicateLocks),
									 &(fc_predlock->xactLink));
				fc_predlock->commitSeqNo = fc_sxact->commitSeqNo;
			}
		}
		else
			fc_RemoveTargetIfNoLongerUsed(fc_target, fc_targettaghash);

		LWLockRelease(fc_partitionLock);

		fc_predlock = fc_nextpredlock;
	}

	/*
	 * 与其逐个删除，不如在我们遍历完列表后重新初始化头部。
	 */
	SHMQueueInit(&fc_sxact->predicateLocks);

	if (IsInParallelMode())
		LWLockRelease(&fc_sxact->perXactPredicateListLock);
	LWLockRelease(SerializablePredicateListLock);

	fc_sxidtag.xid = fc_sxact->topXid;
	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

	/* 释放所有outConflicts（除非'partial'为真） */
	if (!fc_partial)
	{
		fc_conflict = (RWConflict)
			SHMQueueNext(&fc_sxact->outConflicts,
						 &fc_sxact->outConflicts,
						 offsetof(RWConflictData, outLink));
		while (fc_conflict)
		{
			fc_nextConflict = (RWConflict)
				SHMQueueNext(&fc_sxact->outConflicts,
							 &fc_conflict->outLink,
							 offsetof(RWConflictData, outLink));
			if (fc_summarize)
				fc_conflict->sxactIn->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN;
			fc_ReleaseRWConflict(fc_conflict);
			fc_conflict = fc_nextConflict;
		}
	}

	/* 释放所有inConflicts。 */
	fc_conflict = (RWConflict)
		SHMQueueNext(&fc_sxact->inConflicts,
					 &fc_sxact->inConflicts,
					 offsetof(RWConflictData, inLink));
	while (fc_conflict)
	{
		fc_nextConflict = (RWConflict)
			SHMQueueNext(&fc_sxact->inConflicts,
						 &fc_conflict->inLink,
						 offsetof(RWConflictData, inLink));
		if (fc_summarize)
			fc_conflict->sxactOut->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
		fc_ReleaseRWConflict(fc_conflict);
		fc_conflict = fc_nextConflict;
	}

	/* 最后，摆脱xid和事务本身的记录。 */
	if (!fc_partial)
	{
		if (fc_sxidtag.xid != InvalidTransactionId)
			hash_search(SerializableXidHash, &fc_sxidtag, HASH_REMOVE, NULL);
		fc_ReleasePredXact(fc_sxact);
	}

	LWLockRelease(SerializableXactHashLock);
}

/*
 * 测试给定的顶级事务是否与
 * 我们当前事务并发（重叠）。
 *
 * 我们仍然需要为SSI识别顶级事务，所以将
 * 该事务传递给此函数，以节省检查快照的
 * subxip数组的开销。
 */
static bool fc_XidIsConcurrent(TransactionId fc_xid)
{
	Snapshot	fc_snap;
	uint32		fc_i;

	Assert(TransactionIdIsValid(fc_xid));
	Assert(!TransactionIdEquals(fc_xid, GetTopTransactionIdIfAny()));

	fc_snap = GetTransactionSnapshot();

	if (TransactionIdPrecedes(fc_xid, fc_snap->xmin))
		return false;

	if (TransactionIdFollowsOrEquals(fc_xid, fc_snap->xmax))
		return true;

	for (fc_i = 0; fc_i < fc_snap->xcnt; fc_i++)
	{
		if (fc_xid == fc_snap->xip[fc_i])
			return true;
	}

	return false;
}

bool CheckForSerializableConflictOutNeeded(Relation fc_relation, Snapshot fc_snapshot)
{
	if (!fc_SerializationNeededForRead(fc_relation, fc_snapshot))
		return false;

	/* 检查是否有人已经决定我们需要消亡 */
	if (SxactIsDoomed(MySerializableXact))
	{
		ereport(ERROR,
				(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
				 errmsg("could not serialize access due to read/write dependencies among transactions"),
				 errdetail_internal("Reason code: Canceled on identification as a pivot, during conflict out checking."),
				 errhint("The transaction might succeed if retried.")));
	}

	return true;
}


/*
 * CheckForSerializableConflictOut
 *		一个表 AM 正在读取一个已被修改的元组。如果它确定
 *		它正在读取的元组版本对我们不可见，它应该传入创建它的事务的顶级 xid。
 *		否则，如果它确定对我们可见，但已被删除或由于更新而有一个更新版本可用，
 *		它应该传入修改事务的顶级 xid。
 *
 * 这个函数将检查与我们自己的事务是否有重叠。如果给定的
 * xid 也是可序列化的，并且事务重叠（即，它们无法看到
 * 彼此的写入），那么我们就遇到了冲突。
 */
void CheckForSerializableConflictOut(Relation fc_relation, TransactionId fc_xid, Snapshot fc_snapshot)
{
	SERIALIZABLEXIDTAG fc_sxidtag;
	SERIALIZABLEXID *fc_sxid;
	SERIALIZABLEXACT *fc_sxact;

	if (!fc_SerializationNeededForRead(fc_relation, fc_snapshot))
		return;

	/* 检查是否有人已经决定我们需要消亡 */
	if (SxactIsDoomed(MySerializableXact))
	{
		ereport(ERROR,
				(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
				 errmsg("could not serialize access due to read/write dependencies among transactions"),
				 errdetail_internal("Reason code: Canceled on identification as a pivot, during conflict out checking."),
				 errhint("The transaction might succeed if retried.")));
	}
	Assert(TransactionIdIsValid(fc_xid));

	if (TransactionIdEquals(fc_xid, GetTopTransactionIdIfAny()))
		return;

	/*
	 * 查找顶级 xid 的 sxact 或摘要信息。
	 */
	fc_sxidtag.xid = fc_xid;
	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
	fc_sxid = (SERIALIZABLEXID *)
		hash_search(SerializableXidHash, &fc_sxidtag, HASH_FIND, NULL);
	if (!fc_sxid)
	{
		/*
		 * 在“正常”SSI结构中未找到事务。 检查它是否被推送到SLRU存储以供“旧提交”事务使用。
		 */
		SerCommitSeqNo fc_conflictCommitSeqNo;

		fc_conflictCommitSeqNo = fc_SerialGetMinConflictCommitSeqNo(fc_xid);
		if (fc_conflictCommitSeqNo != 0)
		{
			if (fc_conflictCommitSeqNo != InvalidSerCommitSeqNo
				&& (!SxactIsReadOnly(MySerializableXact)
					|| fc_conflictCommitSeqNo
					<= MySerializableXact->SeqNo.lastCommitBeforeSnapshot))
				ereport(ERROR,
						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
						 errmsg("could not serialize access due to read/write dependencies among transactions"),
						 errdetail_internal("Reason code: Canceled on conflict out to old pivot %u.", fc_xid),
						 errhint("The transaction might succeed if retried.")));

			if (SxactHasSummaryConflictIn(MySerializableXact)
				|| !SHMQueueEmpty(&MySerializableXact->inConflicts))
				ereport(ERROR,
						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
						 errmsg("could not serialize access due to read/write dependencies among transactions"),
						 errdetail_internal("Reason code: Canceled on identification as a pivot, with conflict out to old committed transaction %u.", fc_xid),
						 errhint("The transaction might succeed if retried.")));

			MySerializableXact->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
		}

		/* 它不是可序列化的或其他不重要的。 */
		LWLockRelease(SerializableXactHashLock);
		return;
	}
	fc_sxact = fc_sxid->myXact;
	Assert(TransactionIdEquals(fc_sxact->topXid, fc_xid));
	if (fc_sxact == MySerializableXact || SxactIsDoomed(fc_sxact))
	{
		/* 不能与我们自己或将会回滚的事务发生冲突。 */
		LWLockRelease(SerializableXactHashLock);
		return;
	}

	/*
	 * 我们与一个事务发生了冲突，该事务又与一个
	 * 概括事务发生了冲突。 该概括事务必须先提交，而我们无法判断
	 * 它在与我们的快照获取相关的提交时间，因此需要取消某个东西。
	 */
	if (SxactHasSummaryConflictOut(fc_sxact))
	{
		if (!SxactIsPrepared(fc_sxact))
		{
			fc_sxact->flags |= SXACT_FLAG_DOOMED;
			LWLockRelease(SerializableXactHashLock);
			return;
		}
		else
		{
			LWLockRelease(SerializableXactHashLock);
			ereport(ERROR,
					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
					 errmsg("could not serialize access due to read/write dependencies among transactions"),
					 errdetail_internal("Reason code: Canceled on conflict out to old pivot."),
					 errhint("The transaction might succeed if retried.")));
		}
	}

	/*
	 * 如果这是一个只读事务，且写入事务已经提交，并且它与一个
	 * 在其之前提交的事务没有读写冲突，则没有冲突。
	 */
	if (SxactIsReadOnly(MySerializableXact)
		&& SxactIsCommitted(fc_sxact)
		&& !SxactHasSummaryConflictOut(fc_sxact)
		&& (!SxactHasConflictOut(fc_sxact)
			|| MySerializableXact->SeqNo.lastCommitBeforeSnapshot < fc_sxact->SeqNo.earliestOutConflictCommit))
	{
		/* 只读事务将优先运行。没有冲突。 */
		LWLockRelease(SerializableXactHashLock);
		return;
	}

	if (!fc_XidIsConcurrent(fc_xid))
	{
		/* 此写入已经在我们的快照中；没有冲突。 */
		LWLockRelease(SerializableXactHashLock);
		return;
	}

	if (fc_RWConflictExists(MySerializableXact, fc_sxact))
	{
		/* 我们不希望在列表中出现重复的冲突记录。 */
		LWLockRelease(SerializableXactHashLock);
		return;
	}

	/*
	 * 标记冲突。 但是首先，如果此冲突创建了一个危险的
	 * 结构，请报告错误。
	 */
	fc_FlagRWConflict(MySerializableXact, fc_sxact);
	LWLockRelease(SerializableXactHashLock);
}

/*
 * 检查特定目标的读写依赖冲突。CheckForSerializableConflictIn()的一个子例程。
 */
static void fc_CheckTargetForConflictsIn(PREDICATELOCKTARGETTAG *fc_targettag)
{
	uint32		fc_targettaghash;
	LWLock	   *fc_partitionLock;
	PREDICATELOCKTARGET *fc_target;
	PREDICATELOCK *fc_predlock;
	PREDICATELOCK *fc_mypredlock = NULL;
	PREDICATELOCKTAG fc_mypredlocktag;

	Assert(MySerializableXact != InvalidSerializableXact);

	/*
	 * 相同的哈希和LW锁适用于锁目标和锁本身。
	 */
	fc_targettaghash = PredicateLockTargetTagHashCode(fc_targettag);
	fc_partitionLock = PredicateLockHashPartitionLock(fc_targettaghash);
	LWLockAcquire(fc_partitionLock, LW_SHARED);
	fc_target = (PREDICATELOCKTARGET *)
		hash_search_with_hash_value(PredicateLockTargetHash,
									fc_targettag, fc_targettaghash,
									HASH_FIND, NULL);
	if (!fc_target)
	{
		/* 没有人锁定这个目标；我们在这里完成了。 */
		LWLockRelease(fc_partitionLock);
		return;
	}

	/*
	 * 重叠事务的每个锁表示一个冲突：对该事务的
	 * 读写依赖。
	 */
	fc_predlock = (PREDICATELOCK *)
		SHMQueueNext(&(fc_target->predicateLocks),
					 &(fc_target->predicateLocks),
					 offsetof(PREDICATELOCK, targetLink));
	LWLockAcquire(SerializableXactHashLock, LW_SHARED);
	while (fc_predlock)
	{
		SHM_QUEUE  *fc_predlocktargetlink;
		PREDICATELOCK *fc_nextpredlock;
		SERIALIZABLEXACT *fc_sxact;

		fc_predlocktargetlink = &(fc_predlock->targetLink);
		fc_nextpredlock = (PREDICATELOCK *)
			SHMQueueNext(&(fc_target->predicateLocks),
						 fc_predlocktargetlink,
						 offsetof(PREDICATELOCK, targetLink));

		fc_sxact = fc_predlock->tag.myXact;
		if (fc_sxact == MySerializableXact)
		{
			/*
			 * 如果我们在一个元组上获得了写锁，则不需要在同一个元组上获取
			 * 谓词（SIREAD）锁。我们可以安全地移除
			 * 我们的SIREAD锁，但我们会推迟执行此操作，直到循环
			 * 结束后，因为这需要升级为独占分区锁。
			 *
			 * 我们不能在子事务中使用此优化，因为
			 * 子事务可能会回滚，我们将没有锁定在顶层。
			 */
			if (!IsSubTransaction()
				&& GET_PREDICATELOCKTARGETTAG_OFFSET(*fc_targettag))
			{
				fc_mypredlock = fc_predlock;
				fc_mypredlocktag = fc_predlock->tag;
			}
		}
		else if (!SxactIsDoomed(fc_sxact)
				 && (!SxactIsCommitted(fc_sxact)
					 || TransactionIdPrecedes(GetTransactionSnapshot()->xmin,
											  fc_sxact->finishedBefore))
				 && !fc_RWConflictExists(fc_sxact, MySerializableXact))
		{
			LWLockRelease(SerializableXactHashLock);
			LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

			/*
			 * 在获取独占锁后重新检查，因为其他
			 * 事务可能已标记冲突。
			 */
			if (!SxactIsDoomed(fc_sxact)
				&& (!SxactIsCommitted(fc_sxact)
					|| TransactionIdPrecedes(GetTransactionSnapshot()->xmin,
											 fc_sxact->finishedBefore))
				&& !fc_RWConflictExists(fc_sxact, MySerializableXact))
			{
				fc_FlagRWConflict(fc_sxact, MySerializableXact);
			}

			LWLockRelease(SerializableXactHashLock);
			LWLockAcquire(SerializableXactHashLock, LW_SHARED);
		}

		fc_predlock = fc_nextpredlock;
	}
	LWLockRelease(SerializableXactHashLock);
	LWLockRelease(fc_partitionLock);

	/*
	 * 如果我们找到需要移除的自己的SIREAD锁，现在就移除它。
	 *
	 * 此时我们的事务已经在关系上获得了RowExclusiveLock，
	 * 所以如果找到的话，我们可以安全地放弃对该元组的谓词锁，
	 * 而不必担心在MVCC信息到达缓冲区之前对该元组进行的另一个写入。
	 */
	if (fc_mypredlock != NULL)
	{
		uint32		fc_predlockhashcode;
		PREDICATELOCK *fc_rmpredlock;

		LWLockAcquire(SerializablePredicateListLock, LW_SHARED);
		if (IsInParallelMode())
			LWLockAcquire(&MySerializableXact->perXactPredicateListLock, LW_EXCLUSIVE);
		LWLockAcquire(fc_partitionLock, LW_EXCLUSIVE);
		LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

		/*
		 * 从共享内存中移除谓词锁，如果在释放锁时没有被移除。
		 * 可能发生的一个情况是来自自动清理的索引。
		 */
		fc_predlockhashcode = PredicateLockHashCodeFromTargetHashCode
			(&fc_mypredlocktag, fc_targettaghash);
		fc_rmpredlock = (PREDICATELOCK *)
			hash_search_with_hash_value(PredicateLockHash,
										&fc_mypredlocktag,
										fc_predlockhashcode,
										HASH_FIND, NULL);
		if (fc_rmpredlock != NULL)
		{
			Assert(fc_rmpredlock == fc_mypredlock);

			SHMQueueDelete(&(fc_mypredlock->targetLink));
			SHMQueueDelete(&(fc_mypredlock->xactLink));

			fc_rmpredlock = (PREDICATELOCK *)
				hash_search_with_hash_value(PredicateLockHash,
											&fc_mypredlocktag,
											fc_predlockhashcode,
											HASH_REMOVE, NULL);
			Assert(fc_rmpredlock == fc_mypredlock);

			fc_RemoveTargetIfNoLongerUsed(fc_target, fc_targettaghash);
		}

		LWLockRelease(SerializableXactHashLock);
		LWLockRelease(fc_partitionLock);
		if (IsInParallelMode())
			LWLockRelease(&MySerializableXact->perXactPredicateListLock);
		LWLockRelease(SerializablePredicateListLock);

		if (fc_rmpredlock != NULL)
		{
			/*
			 * 如果存在，则在本地锁表中移除条目。如果不存在也没关系；
			 * 这意味着该锁被不同的后端转移到了新的目标。
			 */
			hash_search_with_hash_value(LocalPredicateLockHash,
										fc_targettag, fc_targettaghash,
										HASH_REMOVE, NULL);

			fc_DecrementParentLocks(fc_targettag);
		}
	}
}

/*
 * CheckForSerializableConflictIn
 *		我们正在写入给定的元组。 如果这表明来自另一个可序列化事务的读写冲突，
 *		则采取适当措施。
 *
 * 跳过检查任何缺少参数的粒度。
 *
 * 如果我们对元组所在的关系或页面有谓词锁，
 * 则元组的更新或删除处于冲突状态。
 */
void CheckForSerializableConflictIn(Relation fc_relation, ItemPointer fc_tid, BlockNumber fc_blkno)
{
	PREDICATELOCKTARGETTAG fc_targettag;

	if (!fc_SerializationNeededForWrite(fc_relation))
		return;

	/* 检查是否有人已经决定我们需要消亡 */
	if (SxactIsDoomed(MySerializableXact))
		ereport(ERROR,
				(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
				 errmsg("could not serialize access due to read/write dependencies among transactions"),
				 errdetail_internal("Reason code: Canceled on identification as a pivot, during conflict in checking."),
				 errhint("The transaction might succeed if retried.")));

	/*
	 * 我们正在进行一次写入，这可能会导致现在或将来的读写冲突。
	 * 记住这一事实。
	 */
	MyXactDidWrite = true;

	/*
	 * 检查锁时，从最细粒度到最粗粒度是非常重要的，
	 * 以便粒度提升不会导致我们错过一个锁。 新的（更粗的）锁将在
	 * 旧的（更细的）锁被释放之前被获得。
	 *
	 * 不可能在检查所有粒度时获取和保持一个锁，因为每个目标
	 * 可能位于不同的分区中。
	 */
	if (fc_tid != NULL)
	{
		SET_PREDICATELOCKTARGETTAG_TUPLE(fc_targettag,
										 fc_relation->rd_node.dbNode,
										 fc_relation->rd_id,
										 ItemPointerGetBlockNumber(fc_tid),
										 ItemPointerGetOffsetNumber(fc_tid));
		fc_CheckTargetForConflictsIn(&fc_targettag);
	}

	if (fc_blkno != InvalidBlockNumber)
	{
		SET_PREDICATELOCKTARGETTAG_PAGE(fc_targettag,
										fc_relation->rd_node.dbNode,
										fc_relation->rd_id,
										fc_blkno);
		fc_CheckTargetForConflictsIn(&fc_targettag);
	}

	SET_PREDICATELOCKTARGETTAG_RELATION(fc_targettag,
										fc_relation->rd_node.dbNode,
										fc_relation->rd_id);
	fc_CheckTargetForConflictsIn(&fc_targettag);
}


/*
 * CheckTableForSerializableConflictIn
 *		整个表正在进行DDL类型的逻辑大删除
 *		如TRUNCATE或DROP TABLE。如果这导致了来自
 *		另一个可串行化事务的读写冲突，则采取适当的措施。
 *
 * 尽管这些操作并不完全在快照隔离的范围内执行，
 * 但它们可以发生在可串行化事务内部，并且在逻辑上发生在
 * 任何读取了因这些操作而被销毁的行之后，因此我们尽量
 * 在SSI下正确序列化。
 *
 * 传入的关系必须是堆关系。对堆的任何粒度的谓词锁将
 * 在此事务中导致读写冲突。
 * 索引上的谓词锁无关紧要，因为它们仅存在于防止
 * 有冲突的插入到索引中，而这是一种大规模的*删除*。
 * 当表被截断或丢弃时，索引也将被截断
 * 或丢弃，我们将在发生时处理索引上的锁。
 *
 * 丢弃或截断表还需要丢弃对堆元组或页面的任何现有
 * 谓词锁，因为它们即将消失。这应该在更改谓词锁
 * 之前完成，因为该事务可能会因为冲突而回滚，
 * 在这种情况下锁的更改是不需要的。（目前，我们实际上
 * 不麻烦去删除一个已丢弃或截断表的现有锁。这可能
 * 会导致一些假阳性，但似乎不值得费力。）
 */
void CheckTableForSerializableConflictIn(Relation fc_relation)
{
	HASH_SEQ_STATUS fc_seqstat;
	PREDICATELOCKTARGET *fc_target;
	Oid			fc_dbId;
	Oid			fc_heapId;
	int			fc_i;

	/*
	 * 如果没有可序列化事务正在运行，则快速退出。
	 * 在不获取锁的情况下检查这是安全的，因为调用者持有
	 * 该关系上的ACCESS EXCLUSIVE锁。在持有此锁的情况下，
	 * 不会有新锁对这里重要的锁。
	 */
	if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
		return;

	if (!fc_SerializationNeededForWrite(fc_relation))
		return;

	/*
	 * 我们正在进行一次写入，这可能会导致现在或将来的读写冲突。
	 * 记住这一事实。
	 */
	MyXactDidWrite = true;

	Assert(fc_relation->rd_index == NULL); /* 不是一个索引关系 */

	fc_dbId = fc_relation->rd_node.dbNode;
	fc_heapId = fc_relation->rd_id;

	LWLockAcquire(SerializablePredicateListLock, LW_EXCLUSIVE);
	for (fc_i = 0; fc_i < NUM_PREDICATELOCK_PARTITIONS; fc_i++)
		LWLockAcquire(PredicateLockHashPartitionLockByIndex(fc_i), LW_SHARED);
	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

	/* 扫描目标列表 */
	hash_seq_init(&fc_seqstat, PredicateLockTargetHash);

	while ((fc_target = (PREDICATELOCKTARGET *) hash_seq_search(&fc_seqstat)))
	{
		PREDICATELOCK *fc_predlock;

		/*
		 * 检查这是否是一个需要注意的目标。
		 */
		if (GET_PREDICATELOCKTARGETTAG_RELATION(fc_target->tag) != fc_heapId)
			continue;			/* 错误的关系ID */
		if (GET_PREDICATELOCKTARGETTAG_DB(fc_target->tag) != fc_dbId)
			continue;			/* 错误的数据库ID */

		/*
		 * 遍历此目标的锁并标记冲突。
		 */
		fc_predlock = (PREDICATELOCK *)
			SHMQueueNext(&(fc_target->predicateLocks),
						 &(fc_target->predicateLocks),
						 offsetof(PREDICATELOCK, targetLink));
		while (fc_predlock)
		{
			PREDICATELOCK *fc_nextpredlock;

			fc_nextpredlock = (PREDICATELOCK *)
				SHMQueueNext(&(fc_target->predicateLocks),
							 &(fc_predlock->targetLink),
							 offsetof(PREDICATELOCK, targetLink));

			if (fc_predlock->tag.myXact != MySerializableXact
				&& !fc_RWConflictExists(fc_predlock->tag.myXact, MySerializableXact))
			{
				fc_FlagRWConflict(fc_predlock->tag.myXact, MySerializableXact);
			}

			fc_predlock = fc_nextpredlock;
		}
	}

	/* 以相反的顺序释放锁 */
	LWLockRelease(SerializableXactHashLock);
	for (fc_i = NUM_PREDICATELOCK_PARTITIONS - 1; fc_i >= 0; fc_i--)
		LWLockRelease(PredicateLockHashPartitionLockByIndex(fc_i));
	LWLockRelease(SerializablePredicateListLock);
}


/*
 * 标记两个可串行化事务之间的读写依赖关系。
 *
 * 调用者有责任确保我们在
 * 事务哈希表上拥有LW锁。
 */
static void fc_FlagRWConflict(SERIALIZABLEXACT *fc_reader, SERIALIZABLEXACT *fc_writer)
{
	Assert(fc_reader != fc_writer);

	/* 首先，查看这个冲突是否导致失败。 */
	fc_OnConflict_CheckForSerializationFailure(fc_reader, fc_writer);

	/* 实际执行冲突标记。 */
	if (fc_reader == OldCommittedSxact)
		fc_writer->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN;
	else if (fc_writer == OldCommittedSxact)
		fc_reader->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
	else
		fc_SetRWConflict(fc_reader, fc_writer);
}

/*----------------------------------------------------------------------------
 * 我们即将向依赖图中添加一个RW边 - 检查这样做是否会引入危险结构，如果是，则中止其中一个事务。
 *
 * 只有在依赖图中存在危险结构的情况下才会发生串行化失败：
 *
 *		Tin ------> Tpivot ------> Tout
 *			  rw			 rw
 *
 * 此外，Tout必须先提交。
 *
 * 另一个优化是，如果Tin被声明为只读（或在没有写入的情况下提交），我们只有在Tout在Tin获取快照之前提交时才会有问题。
----------------------------------------------------------------------------
 */
static void fc_OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *fc_reader,
										SERIALIZABLEXACT *fc_writer)
{
	bool		fc_failure;
	RWConflict	fc_conflict;

	Assert(LWLockHeldByMe(SerializableXactHashLock));

	fc_failure = false;

	/*------------------------------------------------------------------------
	 * 检查是否存在已提交的具有RW冲突的写入者（冲突标志为W表示T2在W之前提交）：
	 *
	 *		R ------> W ------> T2
	 *			rw		  rw
	 *
	 * 这是一个危险结构，因此我们必须中止。（由于写入者已提交，因此我们必须是读取者）
	 *------------------------------------------------------------------------
	 */
	if (SxactIsCommitted(fc_writer)
		&& (SxactHasConflictOut(fc_writer) || SxactHasSummaryConflictOut(fc_writer)))
		fc_failure = true;

	/*------------------------------------------------------------------------
	 * 检查写入者是否已成为具有已提交冲突的T2的枢轴，并且T2先提交：
	 *
	 *		R ------> W ------> T2
	 *			rw		  rw
	 *
	 * 由于T2必须先提交，如果满足以下条件则不会出现异常：
	 * - 读取者在T2之前提交
	 * - 写入者在T2之前提交
	 * - 读取者是只读事务，并且读取者与T2并行（= 读取者在T2提交之前获取了快照）
	 *
	 * 我们还在这里处理T2已准备但尚未提交的情况。在这种情况下，T2已经检查过冲突，因此如果它首先提交，使上述冲突成为现实，则它已经太晚了，不能中止。
	 *------------------------------------------------------------------------
	 */
	if (!fc_failure)
	{
		if (SxactHasSummaryConflictOut(fc_writer))
		{
			fc_failure = true;
			fc_conflict = NULL;
		}
		else
			fc_conflict = (RWConflict)
				SHMQueueNext(&fc_writer->outConflicts,
							 &fc_writer->outConflicts,
							 offsetof(RWConflictData, outLink));
		while (fc_conflict)
		{
			SERIALIZABLEXACT *fc_t2 = fc_conflict->sxactIn;

			if (SxactIsPrepared(fc_t2)
				&& (!SxactIsCommitted(fc_reader)
					|| fc_t2->prepareSeqNo <= fc_reader->commitSeqNo)
				&& (!SxactIsCommitted(fc_writer)
					|| fc_t2->prepareSeqNo <= fc_writer->commitSeqNo)
				&& (!SxactIsReadOnly(fc_reader)
					|| fc_t2->prepareSeqNo <= fc_reader->SeqNo.lastCommitBeforeSnapshot))
			{
				fc_failure = true;
				break;
			}
			fc_conflict = (RWConflict)
				SHMQueueNext(&fc_writer->outConflicts,
							 &fc_conflict->outLink,
							 offsetof(RWConflictData, outLink));
		}
	}

	/*------------------------------------------------------------------------
	 * 检查读取者是否已成为与已提交（或已准备）的写入者的枢轴：
	 *
	 *		T0 ------> R ------> W
	 *			 rw		   rw
	 *
	 * 由于W必须先提交以导致异常，如果满足以下条件则不会出现异常：
	 * - T0在写入者之前提交
	 * - T0是只读，并且与写入者重叠
	 *------------------------------------------------------------------------
	 */
	if (!fc_failure && SxactIsPrepared(fc_writer) && !SxactIsReadOnly(fc_reader))
	{
		if (SxactHasSummaryConflictIn(fc_reader))
		{
			fc_failure = true;
			fc_conflict = NULL;
		}
		else
			fc_conflict = (RWConflict)
				SHMQueueNext(&fc_reader->inConflicts,
							 &fc_reader->inConflicts,
							 offsetof(RWConflictData, inLink));
		while (fc_conflict)
		{
			SERIALIZABLEXACT *fc_t0 = fc_conflict->sxactOut;

			if (!SxactIsDoomed(fc_t0)
				&& (!SxactIsCommitted(fc_t0)
					|| fc_t0->commitSeqNo >= fc_writer->prepareSeqNo)
				&& (!SxactIsReadOnly(fc_t0)
					|| fc_t0->SeqNo.lastCommitBeforeSnapshot >= fc_writer->prepareSeqNo))
			{
				fc_failure = true;
				break;
			}
			fc_conflict = (RWConflict)
				SHMQueueNext(&fc_reader->inConflicts,
							 &fc_conflict->inLink,
							 offsetof(RWConflictData, inLink));
		}
	}

	if (fc_failure)
	{
		/*
		 * 我们必须中止一个事务以避免可能的异常发生。如果写入者是我们，我们可以直接ereport()来导致事务中止。否则，我们将标记写入者以便终止，
		 * 使其在尝试提交时中止。然而，如果写入者是一个已准备好的事务，已经准备，我们无法再中止它，因此我们必须杀死读取者。
		 */
		if (MySerializableXact == fc_writer)
		{
			LWLockRelease(SerializableXactHashLock);
			ereport(ERROR,
					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
					 errmsg("could not serialize access due to read/write dependencies among transactions"),
					 errdetail_internal("Reason code: Canceled on identification as a pivot, during write."),
					 errhint("The transaction might succeed if retried.")));
		}
		else if (SxactIsPrepared(fc_writer))
		{
			LWLockRelease(SerializableXactHashLock);

			/* 如果我们不是写入者，我们必须是读取者 */
			Assert(MySerializableXact == fc_reader);
			ereport(ERROR,
					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
					 errmsg("could not serialize access due to read/write dependencies among transactions"),
					 errdetail_internal("Reason code: Canceled on conflict out to pivot %u, during read.", fc_writer->topXid),
					 errhint("The transaction might succeed if retried.")));
		}
		fc_writer->flags |= SXACT_FLAG_DOOMED;
	}
}

/*
 * PreCommit_CheckForSerializationFailure
 *		在提交时检查可串行化事务中的危险结构。
 *
 * 我们在记录每个冲突时检查危险结构。我们在提交时唯一可能出现问题的方式是，如果这是枢轴的“出”侧，并且“入”侧和枢轴都尚未提交。
 *
 * 如果发现危险结构，枢轴（近冲突）将被标记为死亡，因为回滚另一个事务可能意味着我们在没有任何进展的情况下失败。该事务正在提交写操作，因此让它提交可确保进展。如果我们取消了远冲突，其可能在重试时立即失败。
 */
void PreCommit_CheckForSerializationFailure(void)
{
	RWConflict	fc_nearConflict;

	if (MySerializableXact == InvalidSerializableXact)
		return;

	Assert(IsolationIsSerializable());

	LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);

	/*
	 * 检查是否其他人已经决定我们需要死亡。由于我们在部分释放时设置了自己的DOOMED标记，因此在这种情况下忽略。
	 */
	if (SxactIsDoomed(MySerializableXact) &&
		!SxactIsPartiallyReleased(MySerializableXact))
	{
		LWLockRelease(SerializableXactHashLock);
		ereport(ERROR,
				(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
				 errmsg("could not serialize access due to read/write dependencies among transactions"),
				 errdetail_internal("Reason code: Canceled on identification as a pivot, during commit attempt."),
				 errhint("The transaction might succeed if retried.")));
	}

	fc_nearConflict = (RWConflict)
		SHMQueueNext(&MySerializableXact->inConflicts,
					 &MySerializableXact->inConflicts,
					 offsetof(RWConflictData, inLink));
	while (fc_nearConflict)
	{
		if (!SxactIsCommitted(fc_nearConflict->sxactOut)
			&& !SxactIsDoomed(fc_nearConflict->sxactOut))
		{
			RWConflict	fc_farConflict;

			fc_farConflict = (RWConflict)
				SHMQueueNext(&fc_nearConflict->sxactOut->inConflicts,
							 &fc_nearConflict->sxactOut->inConflicts,
							 offsetof(RWConflictData, inLink));
			while (fc_farConflict)
			{
				if (fc_farConflict->sxactOut == MySerializableXact
					|| (!SxactIsCommitted(fc_farConflict->sxactOut)
						&& !SxactIsReadOnly(fc_farConflict->sxactOut)
						&& !SxactIsDoomed(fc_farConflict->sxactOut)))
				{
					/*
					 * 通常，我们中止枢轴事务以确保如果失败的事务重试时我们能够取得进展。
					 * 然而，如果它已经准备好，我们就不能中止它，因此在这种情况下我们选择自杀。
					 */
					if (SxactIsPrepared(fc_nearConflict->sxactOut))
					{
						LWLockRelease(SerializableXactHashLock);
						ereport(ERROR,
								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
								 errmsg("could not serialize access due to read/write dependencies among transactions"),
								 errdetail_internal("Reason code: Canceled on commit attempt with conflict in from prepared pivot."),
								 errhint("The transaction might succeed if retried.")));
					}
					fc_nearConflict->sxactOut->flags |= SXACT_FLAG_DOOMED;
					break;
				}
				fc_farConflict = (RWConflict)
					SHMQueueNext(&fc_nearConflict->sxactOut->inConflicts,
								 &fc_farConflict->inLink,
								 offsetof(RWConflictData, inLink));
			}
		}

		fc_nearConflict = (RWConflict)
			SHMQueueNext(&MySerializableXact->inConflicts,
						 &fc_nearConflict->inLink,
						 offsetof(RWConflictData, inLink));
	}

	MySerializableXact->prepareSeqNo = ++(PredXact->LastSxactCommitSeqNo);
	MySerializableXact->flags |= SXACT_FLAG_PREPARED;

	LWLockRelease(SerializableXactHashLock);
}

/*------------------------------------------------------------------------*/

/*
 * 两阶段提交支持
 */

/*
 * AtPrepare_Locks
 *		为PREPARE做准备工作：为当前持有的所有谓词锁创建2PC状态文件记录。
 */
void AtPrepare_PredicateLocks(void)
{
	PREDICATELOCK *fc_predlock;
	SERIALIZABLEXACT *fc_sxact;
	TwoPhasePredicateRecord fc_record;
	TwoPhasePredicateXactRecord *fc_xactRecord;
	TwoPhasePredicateLockRecord *fc_lockRecord;

	fc_sxact = MySerializableXact;
	fc_xactRecord = &(fc_record.data.xactRecord);
	fc_lockRecord = &(fc_record.data.lockRecord);

	if (MySerializableXact == InvalidSerializableXact)
		return;

	/* 为我们的SERIALIZABLEXACT生成一个xact记录 */
	fc_record.type = TWOPHASEPREDICATERECORD_XACT;
	fc_xactRecord->xmin = MySerializableXact->xmin;
	fc_xactRecord->flags = MySerializableXact->flags;

	/*
	 * 请注意，我们在状态文件中不包括冲突列表，因为即使在事务准备后也可能添加新冲突。我们将在恢复期间做一个保守的假设。
	 */

	RegisterTwoPhaseRecord(TWOPHASE_RM_PREDICATELOCK_ID, 0,
						   &fc_record, sizeof(fc_record));

	/*
	 * 为每个锁生成锁记录。
	 *
	 * 为此，我们需要遍历sxact中的谓词锁列表，而不是使用本地谓词锁表，因为后者不保证准确。
	 */
	LWLockAcquire(SerializablePredicateListLock, LW_SHARED);

	
/*
	 * 在并行模式下无需获取 sxact->perXactPredicateListLock
	 * 因为在我们准备事务时不会有任何并行工作进程运行。
	 */
	Assert(!IsParallelWorker() && !ParallelContextActive());

	fc_predlock = (PREDICATELOCK *)
		SHMQueueNext(&(fc_sxact->predicateLocks),
					 &(fc_sxact->predicateLocks),
					 offsetof(PREDICATELOCK, xactLink));

	while (fc_predlock != NULL)
	{
		fc_record.type = TWOPHASEPREDICATERECORD_LOCK;
		fc_lockRecord->target = fc_predlock->tag.myTarget->tag;

		RegisterTwoPhaseRecord(TWOPHASE_RM_PREDICATELOCK_ID, 0,
							   &fc_record, sizeof(fc_record));

		fc_predlock = (PREDICATELOCK *)
			SHMQueueNext(&(fc_sxact->predicateLocks),
						 &(fc_predlock->xactLink),
						 offsetof(PREDICATELOCK, xactLink));
	}

	LWLockRelease(SerializablePredicateListLock);
}

/*
 * PostPrepare_Locks
 *		在成功的 PREPARE 后进行清理。与非谓词锁管理器不同，
 *		我们不需要将锁转移到虚拟 PGPROC，因为我们的 
 *		SERIALIZABLEXACT 反正会保留。我们只需要清理我们的本地状态。
 */
void PostPrepare_PredicateLocks(TransactionId fc_xid)
{
	if (MySerializableXact == InvalidSerializableXact)
		return;

	Assert(SxactIsPrepared(MySerializableXact));

	MySerializableXact->pid = 0;
	MySerializableXact->pgprocno = INVALID_PGPROCNO;

	hash_destroy(LocalPredicateLockHash);
	LocalPredicateLockHash = NULL;

	MySerializableXact = InvalidSerializableXact;
	MyXactDidWrite = false;
}

/*
 * PredicateLockTwoPhaseFinish
 *		一旦准备好的事务提交或中止，释放其谓词锁。
 */
void PredicateLockTwoPhaseFinish(TransactionId fc_xid, bool fc_isCommit)
{
	SERIALIZABLEXID *fc_sxid;
	SERIALIZABLEXIDTAG fc_sxidtag;

	fc_sxidtag.xid = fc_xid;

	LWLockAcquire(SerializableXactHashLock, LW_SHARED);
	fc_sxid = (SERIALIZABLEXID *)
		hash_search(SerializableXidHash, &fc_sxidtag, HASH_FIND, NULL);
	LWLockRelease(SerializableXactHashLock);

	/* 如果不是一个可串行化事务，则找不到 xid */
	if (fc_sxid == NULL)
		return;

	/* 释放其锁 */
	MySerializableXact = fc_sxid->myXact;
	MyXactDidWrite = true;		/* 保守地假设我们写了
								 * 某些内容 */
	ReleasePredicateLocks(fc_isCommit, false);
}

/*
 * 重新获取属于已准备事务的谓词锁。
 */
void predicatelock_twophase_recover(TransactionId fc_xid, uint16 fc_info,
							   void *fc_recdata, uint32 fc_len)
{
	TwoPhasePredicateRecord *fc_record;

	Assert(fc_len == sizeof(TwoPhasePredicateRecord));

	fc_record = (TwoPhasePredicateRecord *) fc_recdata;

	Assert((fc_record->type == TWOPHASEPREDICATERECORD_XACT) ||
		   (fc_record->type == TWOPHASEPREDICATERECORD_LOCK));

	if (fc_record->type == TWOPHASEPREDICATERECORD_XACT)
	{
		/* 每个事务记录。设置一个 SERIALIZABLEXACT。 */
		TwoPhasePredicateXactRecord *fc_xactRecord;
		SERIALIZABLEXACT *fc_sxact;
		SERIALIZABLEXID *fc_sxid;
		SERIALIZABLEXIDTAG fc_sxidtag;
		bool		fc_found;

		fc_xactRecord = (TwoPhasePredicateXactRecord *) &fc_record->data.xactRecord;

		LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
		fc_sxact = fc_CreatePredXact();
		if (!fc_sxact)
#ifdef FDD //cppcheck
		{
#endif
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of shared memory")));
#ifdef FDD //cppcheck
			return;
		}
#endif
		/* 已准备事务的 vxid 是 InvalidBackendId/xid；没有 pid */
		fc_sxact->vxid.backendId = InvalidBackendId;
		fc_sxact->vxid.localTransactionId = (LocalTransactionId) fc_xid;
		fc_sxact->pid = 0;
		fc_sxact->pgprocno = INVALID_PGPROCNO;

		/* 一个已准备的事务尚未提交 */
		fc_sxact->prepareSeqNo = RecoverySerCommitSeqNo;
		fc_sxact->commitSeqNo = InvalidSerCommitSeqNo;
		fc_sxact->finishedBefore = InvalidTransactionId;

		fc_sxact->SeqNo.lastCommitBeforeSnapshot = RecoverySerCommitSeqNo;

		/*
		 * 不需要跟踪这个；在恢复事务开始时运行的事务都不再活跃，
		 * 除了可能的其他已准备事务，而我们并不关心这些事务是否是 RO_SAFE。
		 */
		SHMQueueInit(&(fc_sxact->possibleUnsafeConflicts));

		SHMQueueInit(&(fc_sxact->predicateLocks));
		SHMQueueElemInit(&(fc_sxact->finishedLink));

		fc_sxact->topXid = fc_xid;
		fc_sxact->xmin = fc_xactRecord->xmin;
		fc_sxact->flags = fc_xactRecord->flags;
		Assert(SxactIsPrepared(fc_sxact));
		if (!SxactIsReadOnly(fc_sxact))
		{
			++(PredXact->WritableSxactCount);
			Assert(PredXact->WritableSxactCount <=
				   (MaxBackends + max_prepared_xacts));
		}

		/*
		 * 我们不知道该事务是否有任何冲突，因此
		 * 我们会保守地假设它既有内部冲突又有外部冲突，并用摘要冲突标志表示。
		 */
		SHMQueueInit(&(fc_sxact->outConflicts));
		SHMQueueInit(&(fc_sxact->inConflicts));
		fc_sxact->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN;
		fc_sxact->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;

		/* 注册事务的 xid */
		fc_sxidtag.xid = fc_xid;
		fc_sxid = (SERIALIZABLEXID *) hash_search(SerializableXidHash,
											   &fc_sxidtag,
											   HASH_ENTER, &fc_found);
		Assert(fc_sxid != NULL);
		Assert(!fc_found);
		fc_sxid->myXact = (SERIALIZABLEXACT *) fc_sxact;

		/*
		 * 更新全局 xmin。注意，相较于注册正常事务，
		 * 这是一个特殊情况，因为全局 xmin 可能会回退。这没关系，
		 * 因为在恢复结束之前我们不会完成任何事务或创建任何未准备
		 * 的事务，因此不会有丢弃的危险。
		 */
		if ((!TransactionIdIsValid(PredXact->SxactGlobalXmin)) ||
			(TransactionIdFollows(PredXact->SxactGlobalXmin, fc_sxact->xmin)))
		{
			PredXact->SxactGlobalXmin = fc_sxact->xmin;
			PredXact->SxactGlobalXminCount = 1;
			fc_SerialSetActiveSerXmin(fc_sxact->xmin);
		}
		else if (TransactionIdEquals(fc_sxact->xmin, PredXact->SxactGlobalXmin))
		{
			Assert(PredXact->SxactGlobalXminCount > 0);
			PredXact->SxactGlobalXminCount++;
		}

		LWLockRelease(SerializableXactHashLock);
	}
	else if (fc_record->type == TWOPHASEPREDICATERECORD_LOCK)
	{
		/* 锁记录。重新创建 PREDICATELOCK */
		TwoPhasePredicateLockRecord *fc_lockRecord;
		SERIALIZABLEXID *fc_sxid;
		SERIALIZABLEXACT *fc_sxact;
		SERIALIZABLEXIDTAG fc_sxidtag;
		uint32		fc_targettaghash;

		fc_lockRecord = (TwoPhasePredicateLockRecord *) &fc_record->data.lockRecord;
		fc_targettaghash = PredicateLockTargetTagHashCode(&fc_lockRecord->target);

		LWLockAcquire(SerializableXactHashLock, LW_SHARED);
		fc_sxidtag.xid = fc_xid;
		fc_sxid = (SERIALIZABLEXID *)
			hash_search(SerializableXidHash, &fc_sxidtag, HASH_FIND, NULL);
		LWLockRelease(SerializableXactHashLock);

		Assert(fc_sxid != NULL);
		fc_sxact = fc_sxid->myXact;
		Assert(fc_sxact != InvalidSerializableXact);

		fc_CreatePredicateLock(&fc_lockRecord->target, fc_targettaghash, fc_sxact);
	}
}

/*
 * 准备将当前 SERIALIZABLEXACT 分享给并行工作进程。
 * 返回一个可以在并行工作进程中使用 AttachSerializableXact() 的句柄对象。
 */
SerializableXactHandle
ShareSerializableXact(void)
{
	return MySerializableXact;
}

/*
 * 允许并行工作者导入领导者的 SERIALIZABLEXACT。
 */
void AttachSerializableXact(SerializableXactHandle fc_handle)
{

	Assert(MySerializableXact == InvalidSerializableXact);

	MySerializableXact = (SERIALIZABLEXACT *) fc_handle;
	if (MySerializableXact != InvalidSerializableXact)
		fc_CreateLocalPredicateLockHash();
}
